You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
configuring mulitple Azure Entra Id Saml2 based IdP's in in middleware, and attempting to invoke chosesn IdP in the Acs Challange, I get the following exception
#1480
My code works perfeclty fine if I enable one or the other IdP's but when I enabled them both at the same time I get the following internal execption:
System.Collections.Generic.KeyNotFoundException: No Idp with entity id "https://sts.windows.net/8c47ef63-1296-4e7a-97b7-649f4eb09330/" found.
---> System.Collections.Generic.KeyNotFoundException: The given key 'Sustainsys.Saml2.Metadata.EntityId' was not present in the dictionary.
at System.Collections.Generic.Dictionary2.get_Item(TKey key) at Sustainsys.Saml2.Configuration.IdentityProviderDictionary.get_Item(EntityId entityId) --- End of inner exception stack trace --- at Sustainsys.Saml2.Configuration.IdentityProviderDictionary.get_Item(EntityId entityId) at Sustainsys.Saml2.Configuration.Saml2Notifications.<>c.<.ctor>b__84_18(EntityId ei, IDictionary2 rd, IOptions opt)
at Sustainsys.Saml2.WebSso.AcsCommand.GetIdpContext(XmlElement xml, HttpRequestData request, IOptions options)
at Sustainsys.Saml2.WebSso.AcsCommand.Run(HttpRequestData request, IOptions options)
at Sustainsys.Saml2.AspNetCore2.Saml2Handler.HandleRequestAsync()
at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddlewareImpl.Invoke(HttpContext context)
Part of the middleware, that iterates over a look of 1 to n IdP's from appsetting.json.
public void ConfigureServices(IServiceCollection services)
{
_logger.LogInformation("Configuring services for SSO: {UseSSO}", _azureEntraOptions.UseSSO);
try
{
services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
if (!_azureEntraOptions.UseSSO)
{
options.DefaultChallengeScheme = _azureEntraOptions.DefaultIdProvider;
_logger.LogInformation("Configured default challenge scheme as: {DefaultIdProvider}", _azureEntraOptions.DefaultIdProvider);
}
})
.AddCookie();
foreach (var idProvider in _azureEntraOptions.IdProviders.Where(ShouldIncludeIdProvider))
{
var idProviderKey = idProvider.Key;
var idProviderOptions = idProvider.Value;
_logger.LogInformation("Adding SAML2 authentication for provider: {IdProviderKey}", idProviderKey);
_logger.LogInformation("Processing identity provider {IdProviderKey} with status Enabled: {Enabled}", idProvider.Key, idProvider.Value.Enabled);
services.AddAuthentication()
.AddSaml2(idProviderKey, options =>
{
ConfigureSaml2Options(options, idProviderOptions.Saml2!);
});
}
//// Log all configured Identity Providers after addition
//services.PostConfigure<Saml2Options>(options =>
//{
// _logger.LogInformation("Configured Identity Providers:");
// var idPs = options.IdentityProviders.KnownIdentityProviders;
// foreach (var idProvider in idPs)
// {
// _logger.LogInformation("Configured IdP EntityId: {EntityId}", idProvider.EntityId.Id);
// }
//});
}
catch (Exception ex)
{
_logger.LogError(ex, "An error occurred while configuring authentication services.");
}
services.AddScoped<IClaimsTransformationService, ClaimsTransformationService>();
}
private bool ShouldIncludeIdProvider(KeyValuePair<string, IdProviderOptions> idProvider)
{
return (_azureEntraOptions.UseSSO && idProvider.Value.Enabled) ||
(!_azureEntraOptions.UseSSO &&
idProvider.Key.Equals(_azureEntraOptions.DefaultIdProvider, StringComparison.OrdinalIgnoreCase));
}
private void ConfigureSaml2Options(Saml2Options options, EntraSaml2Options entraSaml2Config)
{
try
{
_logger.LogInformation("Attempting to add IdentityProvider with EntityId: {EntityId}", entraSaml2Config.MicrosoftEntraIdentifier);
options.SPOptions.EntityId = new EntityId(entraSaml2Config.EntityId);
options.SPOptions.ReturnUrl = new Uri(_azureEntraOptions.AcsReturnUrl!);
options.SPOptions.PublicOrigin = new Uri(_azureEntraOptions.PublicOrigin);
var identityProvider = new IdentityProvider(
new EntityId(entraSaml2Config.MicrosoftEntraIdentifier),
options.SPOptions)
{
LoadMetadata = true,
MetadataLocation = entraSaml2Config.IdPMetadata,
SingleSignOnServiceUrl = new Uri(entraSaml2Config.LoginURL!),
SingleLogoutServiceUrl = new Uri(entraSaml2Config.LogoutURL!),
AllowUnsolicitedAuthnResponse = entraSaml2Config.AllowUnsolicitedAuthnResponse
};
if (_azureEntraOptions.ForceLoginPrompt)
{
options.Notifications.AuthenticationRequestCreated = (request, provider, dictionary) =>
{
request.ForceAuthentication = true;
};
}
_logger.LogInformation("Registering IdP with EntityId.Id: {Id}", identityProvider.EntityId.Id);
options.IdentityProviders.Add(identityProvider);
options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
_logger.LogInformation("Configuring SAML2 options for EntityId: {EntityId}", entraSaml2Config.EntityId);
_logger.LogInformation("AcsReturnUrl: {AcsReturnUrl}", _azureEntraOptions.AcsReturnUrl);
_logger.LogInformation("PublicOrigin: {PublicOrigin}", _azureEntraOptions.PublicOrigin);
_logger.LogInformation("MicrosoftEntraIdentifier: {MicrosoftEntraIdentifier}", entraSaml2Config.MicrosoftEntraIdentifier);
_logger.LogInformation("IdPMetadata: {IdPMetadata}", entraSaml2Config.IdPMetadata);
_logger.LogInformation("LoginURL: {LoginURL}", entraSaml2Config.LoginURL);
_logger.LogInformation("LogoutURL: {LogoutURL}", entraSaml2Config.LogoutURL);
_logger.LogInformation("AllowUnsolicitedAuthnResponse: {AllowUnsolicitedAuthnResponse}", entraSaml2Config.AllowUnsolicitedAuthnResponse);
_logger.LogInformation("ForceAuthentication: {ForceAuthentication}", _azureEntraOptions.ForceLoginPrompt);
}
catch (UriFormatException uriEx)
{
_logger.LogError(uriEx, "Invalid URI format in SAML2 configuration.");
throw new ConfigurationErrorsException("Invalid URI in SAML2 configuration. Check the URLs in your settings.", uriEx);
}
catch (ArgumentNullException argEx)
{
_logger.LogError(argEx, "A required argument is null in SAML2 configuration.");
throw new ConfigurationErrorsException("A required value in the SAML2 configuration is missing. Check your configuration.", argEx);
}
catch (Exception ex)
{
_logger.LogError(ex, "An unexpected error occurred while configuring SAML2 options.");
throw new ConfigurationErrorsException("An unexpected error occurred during SAML2 configuration. See the logs for details.", ex);
}
}
[HttpGet("login")]
[RequiresAddClaimMiddleware]
public IActionResult Login([FromQuery] string selectionKey, [FromQuery] string selectedIdProvider)
{
try
{
_logger.LogInformation("Login endpoint called with SelectionKey: {SelectionKey}, SelectedIdProvider: {SelectedIdProvider}", selectionKey, selectedIdProvider);
// Update claims based on selection key
UpdateSelectionKeyClaim(selectionKey);
_logger.LogDebug("Updated selection key claim for SelectionKey: {SelectionKey}", selectionKey);
if (User?.Identity?.IsAuthenticated != true)
{
_logger.LogInformation("User is not authenticated. Redirecting to SAML Challenge.");
// Construct the redirect URI to be used after successful login
// we use this to transfer state state into the auth'd realm
var redirectToCallbackUri = Url.Action("Callback", "Saml", new { SelectionKey = selectionKey, SelectedIdProvider = selectedIdProvider });
_logger.LogDebug("Constructed redirect to callback URI for SAML Challenge: {RedirectUri}", redirectToCallbackUri);
// If redirectUri is null, log the issue
if (redirectToCallbackUri == null)
{
string redirectUriMessage = "Redirect URI is null. Cannot proceed with SAML Challenge.";
_logger.LogError(redirectUriMessage);
return StatusCode(StatusCodes.Status500InternalServerError, "Failed to construct redirect URI for SAML Challenge.");
}
// Redirect to SAML Challenge
var properties = new AuthenticationProperties
{
RedirectUri = redirectToCallbackUri
};
return Challenge(properties, selectedIdProvider);
}
string message = $"User is already authenticated with SelectionKey: {selectionKey}, returning authenticated status.";
_logger.LogInformation(message);
return Ok("User is already authenticated.");
}
catch (Exception ex)
{
string errorMessage = $"Error in SAML Login with SelectionKey: {selectionKey}, SelectedIdProvider: {selectedIdProvider}. Exception Message: {ex.Message}";
_logger.LogError(ex, errorMessage);
return StatusCode(StatusCodes.Status500InternalServerError, $"Internal server error: {ex.Message}");
}
}
[Authorize]
[HttpGet("Callback")]
public async Task<IActionResult> Callback([FromQuery] string selectionKey, [FromQuery] string selectedIdProvider)
{
_logger.LogInformation("ACS endpoint called with SelectionKey: {SelectionKey}, SelectedIdProvider: {SelectedIdProvider}", selectionKey, selectedIdProvider);
try
{
// Start by authenticating the user
var result = await HttpContext.AuthenticateAsync(CookieAuthenticationDefaults.AuthenticationScheme);
_logger.LogDebug("Authentication result obtained: Succeeded - {AuthSucceeded}", result?.Succeeded);
if (!IsAuthenticationValid(result))
{
_logger.LogWarning("Authentication failed at ACS endpoint. SelectionKey: {SelectionKey}, SelectedIdProvider: {SelectedIdProvider}", selectionKey, selectedIdProvider);
return Unauthorized(new { Message = "Authentication failed." });
}
_logger.LogInformation("Authentication succeeded for ACS endpoint with SelectionKey: {SelectionKey}, SelectedIdProvider: {SelectedIdProvider}", selectionKey, selectedIdProvider);
// Update selection key claim in the authenticated user's principal
UpdateSelectionKeyClaim(selectionKey, result.Principal);
_logger.LogDebug("Selection key claim updated in principal: {SelectionKey}", selectionKey);
// Attempt to transform the user's claims
var transformedPrincipal = await _claimsTransformation.TransformAsync(result.Principal!);
if (transformedPrincipal != null)
{
var claims = new List<Claim>(transformedPrincipal.Claims)
{
new("Database", selectionKey),
new("IdP", selectedIdProvider)
};
var claimsIdentity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
_logger.LogInformation("Claims transformation succeeded. Prepared claims for sign-in.");
// Log the claims being added (ensure sensitive information is handled appropriately)
foreach (var claim in claims)
{
_logger.LogDebug("Claim added: {ClaimType} - {ClaimValue}", claim.Type, claim.Value);
}
// Signing in the user
await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, new ClaimsPrincipal(claimsIdentity), result.Properties);
_logger.LogInformation("User signed in successfully after SAML authentication. SelectionKey: {SelectionKey}", selectionKey);
return Ok();
}
else
{
_logger.LogWarning("Claims transformation returned null. User will be signed out. SelectionKey: {SelectionKey}", selectionKey);
result.Principal?.RemoveSelectionKeyClaim();
await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
_logger.LogInformation("User signed out due to unsuccessful claims transformation.");
var properties = new AuthenticationProperties
{
RedirectUri = "/"
};
var claimsIdentity = result.Principal.Identity as ClaimsIdentity;
var idPClaim = claimsIdentity?.Claims.FirstOrDefault(c => c.Type == "IdP");
// Remove the "IdP" claim if it exists
if (claimsIdentity != null && idPClaim != null)
{
claimsIdentity.RemoveClaim(idPClaim);
}
_logger.LogInformation("Redirecting to home after failed claims transformation. IdP: {IdP}", idPClaim.Value);
return SignOut(properties, CookieAuthenticationDefaults.AuthenticationScheme, idPClaim.Value);
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Exception occurred in ACS endpoint: {Message} | SelectionKey: {SelectionKey}, SelectedIdProvider: {SelectedIdProvider}", ex.Message, selectionKey, selectedIdProvider);
return await HandleAcsException(ex, $"Error in ACS endpoint: {ex.Message}");
}
}
joshuafranklinengineeringsystems
changed the title
configuring mulitple IdP's in in middleware, and attempting to invoke chosesn IdP in the Acs Challange, I get the following exception
configuring mulitple Entra Id Saml2 based IdP's in in middleware, and attempting to invoke chosesn IdP in the Acs Challange, I get the following exception
Dec 3, 2024
joshuafranklinengineeringsystems
changed the title
configuring mulitple Entra Id Saml2 based IdP's in in middleware, and attempting to invoke chosesn IdP in the Acs Challange, I get the following exception
configuring mulitple Azure Entra Id Saml2 based IdP's in in middleware, and attempting to invoke chosesn IdP in the Acs Challange, I get the following exception
Dec 3, 2024
My code works perfeclty fine if I enable one or the other IdP's but when I enabled them both at the same time I get the following internal execption:
System.Collections.Generic.KeyNotFoundException: No Idp with entity id "https://sts.windows.net/8c47ef63-1296-4e7a-97b7-649f4eb09330/" found.
---> System.Collections.Generic.KeyNotFoundException: The given key 'Sustainsys.Saml2.Metadata.EntityId' was not present in the dictionary.
at System.Collections.Generic.Dictionary
2.get_Item(TKey key) at Sustainsys.Saml2.Configuration.IdentityProviderDictionary.get_Item(EntityId entityId) --- End of inner exception stack trace --- at Sustainsys.Saml2.Configuration.IdentityProviderDictionary.get_Item(EntityId entityId) at Sustainsys.Saml2.Configuration.Saml2Notifications.<>c.<.ctor>b__84_18(EntityId ei, IDictionary
2 rd, IOptions opt)at Sustainsys.Saml2.WebSso.AcsCommand.GetIdpContext(XmlElement xml, HttpRequestData request, IOptions options)
at Sustainsys.Saml2.WebSso.AcsCommand.Run(HttpRequestData request, IOptions options)
at Sustainsys.Saml2.AspNetCore2.Saml2Handler.HandleRequestAsync()
at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddlewareImpl.Invoke(HttpContext context)
Part of the middleware, that iterates over a look of 1 to n IdP's from appsetting.json.
appsetting.json:
The text was updated successfully, but these errors were encountered: