I am a SAML rookie using .Net Core 3.1 and latest version of itfoxtec libraries from Nuget. SP-initiated SSO. I have the login route working, but I am getting an exception on the Assertion Consumer Service method. Can anyone give me any ideas on where to look?
Jan 5/2024 what I've found is that .Saml2TokenSerializer.ReadAuthenticationContext is firing twice back to back and it generates the exception on this line:
var authnContext = new Saml2AuthenticationContext();
I've found that if I change it to the following it works (it still fires twice but the process is successful):
var authnContext = new Saml2AuthenticationContext(classRef);
ITFoxTec version #
This is the Startup.cs code:
IdentityModelEventSource.ShowPII = true;
services.BindConfig<Saml2Configuration>(Configuration, "Saml2", (serviceProvider, saml2Configuration) =>
{
saml2Configuration.AllowedAudienceUris.Add(saml2Configuration.Issuer);
var httpClientFactory = serviceProvider.GetService<IHttpClientFactory>();
var entityDescriptor = new EntityDescriptor();
entityDescriptor.ReadIdPSsoDescriptorFromUrlAsync(httpClientFactory, new Uri(Configuration["Saml2:IdPMetadata"])).GetAwaiter().GetResult();
if (entityDescriptor.IdPSsoDescriptor != null)
{
saml2Configuration.AllowedIssuer = entityDescriptor.EntityId;
saml2Configuration.SingleSignOnDestination = entityDescriptor.IdPSsoDescriptor.SingleSignOnServices.First().Location;
saml2Configuration.SingleLogoutDestination = entityDescriptor.IdPSsoDescriptor.SingleLogoutServices.First().Location;
foreach (var signingCertificate in entityDescriptor.IdPSsoDescriptor.SigningCertificates)
{
if (signingCertificate.IsValidLocalTime())
{
saml2Configuration.SignatureValidationCertificates.Add(signingCertificate);
}
}
if (saml2Configuration.SignatureValidationCertificates.Count <= 0)
{
throw new Exception("The IdP signing certificates has expired.");
}
if (entityDescriptor.IdPSsoDescriptor.WantAuthnRequestsSigned.HasValue)
{
saml2Configuration.SignAuthnRequest = entityDescriptor.IdPSsoDescriptor.WantAuthnRequestsSigned.Value;
}
}
else
{
throw new Exception("IdPSsoDescriptor not loaded from metadata.");
}
return saml2Configuration;
});
services.AddSaml2(slidingExpiration: true);
services.AddHttpClient();
This is the code in the Assertion Consumer Service method (copied and implemented directly from itfoxtec example in Github):
[Route("ACS")]
public async Task<IActionResult> AssertionConsumerService()
{
var httpRequest = Request.ToGenericHttpRequest(validate: true);
var saml2AuthnResponse = new Saml2AuthnResponse(config);
httpRequest.Binding.ReadSamlResponse(httpRequest, saml2AuthnResponse);
if (saml2AuthnResponse.Status != Saml2StatusCodes.Success)
{
throw new AuthenticationException($"SAML Response status: {saml2AuthnResponse.Status}");
}
httpRequest.Binding.Unbind(httpRequest, saml2AuthnResponse);
await saml2AuthnResponse.CreateSession(HttpContext, claimsTransform: (claimsPrincipal) => ClaimsTransform.Transform(claimsPrincipal));
var relayStateQuery = httpRequest.Binding.GetRelayStateQuery();
var returnUrl = relayStateQuery.ContainsKey(relayStateReturnUrl) ? relayStateQuery[relayStateReturnUrl] : Url.Content("~/");
return Redirect(returnUrl);
}
This is the idp metadata:
<EntityDescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata" validUntil="2024-01-24T19:58:59.922Z" cacheDuration="PT48H" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:attr="urn:oasis:names:tc:SAML:metadata:attribute" entityID="https://services.*****/fed/saml2/jsp/exportmetadata.jsp">
<IDPSSODescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata" validUntil="2024-01-24T19:58:59.921551052Z" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
<KeyDescriptor use="signing">
<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
<X509Data>
*****
</X509Data>
</KeyInfo>
</KeyDescriptor>
<KeyDescriptor use="encryption">
<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
<X509Data>
*****
</X509Data>
</KeyInfo>
<EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#aes128-cbc"></EncryptionMethod>
<EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#aes192-cbc"></EncryptionMethod>
<EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#aes256-cbc"></EncryptionMethod>
<EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p"></EncryptionMethod>
</KeyDescriptor>
<SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="https://services.*****/fed/saml2/idpSingleLogout"></SingleLogoutService>
<SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://services.*****/fed/saml2/idpSingleLogout"></SingleLogoutService>
<NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:persistent</NameIDFormat>
<NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:transient</NameIDFormat>
<NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress</NameIDFormat>
<NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:persistent-paiidentifier</NameIDFormat>
<SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="https://services.*****/fed/SSORedirect/metaAlias/idp"></SingleSignOnService>
<SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://services.*****/fed/SSORedirect/metaAlias/idp"></SingleSignOnService>
</IDPSSODescriptor>
</EntityDescriptor>
This is the metadata I provided to the idp:
<m:EntityDescriptor xmlns:m="urn:oasis:names:tc:SAML:2.0:metadata" entityID="https://*****-uat.*****.ca">
<m:SPSSODescriptor protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol" WantAssertionsSigned="false" AuthnRequestsSigned="false">
<m:SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="https://*****-uat.*****.ca/SAML/Logout"/>
<m:NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:persistent</m:NameIDFormat>
<m:AssertionConsumerService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://*****-uat.*****.ca/SAML/ACS" index="0" isDefault="true"/>
</m:SPSSODescriptor>
</m:EntityDescriptor>
This the redacted SAML response from the idp:
<samlp:Response xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:xs="http://www.w3.org/2001/XMLSchema" ID="id-24716b3bb8ce9969bddf6005ee12887538005094" InResponseTo="_880f20cf-797f-4d8a-81e8-79530a281fc6" Version="2.0" IssueInstant="2023-12-28T14:58:28.149Z" Destination="https://*****-uat.******.ca/SAML/ACS">
<saml:Issuer Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity">https://*****/fed/saml2/jsp/exportmetadata.jsp</saml:Issuer>
<ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:SignedInfo>
<ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" />
<ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256" />
<ds:Reference URI="#id-24716b3bb8ce9969bddf6005ee12887538005094">
<ds:Transforms>
<ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature" />
<ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" />
</ds:Transforms>
<ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256" />
<ds:DigestValue>ayY7AA8OLAlYqntPRLStVyq1RtyK3QC2SpslvO25FLQ=</ds:DigestValue>
</ds:Reference>
</ds:SignedInfo>
<ds:SignatureValue>Pm6z6j54xIuoP9RSHgnZszkDYKIbv0ZGKmxOPvAJQhVvnvq8GDftFpIIfNs6N+lqwEY5BMmhDo2lbExWzXBBsQ0vrhd+IbG6xBx+QWIb0CcKraQD0SadiOvi1SmJgZMLre6RCoH9gVla4fLr5EZq2QibjrjY7Ewj4KSzBhv3Jr7EKNd9n4iq1+joY9znlg/LZBNhjBnn/q0d8iLuGTKxt1Bsivbrs/nPabmuHai3jMMu0ht5+j4vWTNERcfWVHaVbjEh1aXnhiusTKA0TEj2rrvW7OXuHwDlhtqbbEjo2ufQjbQbN08c3YcyYFOuwWk3e/GQqQxzBT0elyjYwdRAV7QbBMRUbeVpQpAiqtpfYw27T98jIu2yg92KgT9J+s6aemVI5ilfSeBBlU/tDeBs2SGybRpNYPjedHjVCbdftCR/9k49pQcwbHkLz0Th4yW0euzUuaXLuEPQthKDGNlGfsEyNjAKyORpbPIngyAZ1K2bwQSg5rVfy1eqeXYUYhbdSv+rc25mqq+L3DNpvhCrP0QCE67OzbNswBgQiHRvUTc0sx69nQpCdKzrFTzG4csnj3RvCOCqST1kFhnARGG+Uyh7TiHANWambUMypp664I0rcpA9tvr4iOsR6IgMdrkOcBcE26rBIdgSNq6WVEsTB45G3vnbd6AQkvBkFDvqzFs=</ds:SignatureValue>
<ds:KeyInfo>
<ds:X509Data>
</ds:X509Data>
</ds:KeyInfo>
</ds:Signature>
<samlp:Status>
<samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success" />
</samlp:Status>
<saml:Assertion xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="id-ea20600bc7422a4e820fcaea5317aecff525e51b" IssueInstant="2023-12-28T14:58:28.132Z" Version="2.0">
<saml:Issuer Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity">https://*****/fed/saml2/jsp/exportmetadata.jsp</saml:Issuer>
<ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:SignedInfo>
<ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" />
<ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256" />
<ds:Reference URI="#id-ea20600bc7422a4e820fcaea5317aecff525e51b">
<ds:Transforms>
<ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature" />
<ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" />
</ds:Transforms>
<ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256" />
<ds:DigestValue>v/ef4bdarCUSn/PBF+VeXgvtpWpO6gfFVLT5C4t3hOM=</ds:DigestValue>
</ds:Reference>
</ds:SignedInfo>
<ds:SignatureValue>Xltc66Y3PMsPf215BM5G2/vYzWSRxgO6dZEkFOeF3b5CBCB9c9NHTFnqJ+CqI3Eyrguq2pwgbR4GXtsBJ0ex/JS8KlKgOp6MFZZb5xtCXh7mIOxuEs8QSlYWd5vYERTTnoGd63rlcnHIS1ELjhL/JlAsOV+u+iVxxX6c40PfVWIFzznVItj5FXopnBjqLCXXH0LKT2EUUu1IVWt1UoDYbXF5DKSE8Og4yKJ0+krzTJ/IoLX8hPudDucRIzzI8hSlNwT8ejbFZo0ZrrCJPSQ9LaWUydYffPMdJWvf8j9wG6KvcO8k9viY21YjBR54j23wak2nlOupKzQuWeFYL+sdknfvacb/NHWa1fD9suQnnl6EaI5TiP6RObI7zA3ziP/SA4zBgQK4JFTiQ4lOQL88NljrvcULuEsyhtweZsbzqSu+t4ZVpfs9tKxgEE32Z2WlXSZw8q+WEK2dc+9R/wh0+G6kk9/MgBuB1Aowg6gSObzawMftilnumjz9uMLLW7rNmUnCr2fowOJljDc2KYXP/sPFY84Q3RlOlSsduarvUwc5g2FfvdutUu3/y1vtE2RhQTyhZmehytNAoe7qilFQEp9zvyOSV9y/EZ+yOJSz85V0TI9hOFNeNwqbm58fOQ7gQBuOiJ6qE61oCSPamakYRq0SudF3L5mInstcgaEV768=</ds:SignatureValue>
<ds:KeyInfo>
<ds:X509Data>
</ds:X509Data>
</ds:KeyInfo>
</ds:Signature>
<saml:Subject>
<saml:NameID Format="urn:oasis:names:tc:SAML:2.0:nameid-format:persistent" NameQualifier="https://*****/fed/saml2/jsp/exportmetadata.jsp" SPNameQualifier="https://*****-uat.******.ca">68814fab-4bff-4a47-98ca-63fbee6a3f5a</saml:NameID>
<saml:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
<saml:SubjectConfirmationData InResponseTo="_880f20cf-797f-4d8a-81e8-79530a281fc6" NotOnOrAfter="2023-12-28T14:59:58.132Z" Recipient="https://*****-uat.******.ca/SAML/ACS" />
</saml:SubjectConfirmation>
</saml:Subject>
<saml:Conditions NotBefore="2023-12-28T14:58:27.08Z" NotOnOrAfter="2023-12-28T14:59:57.08Z">
<saml:AudienceRestriction>
<saml:Audience>https://*****-uat.******.ca</saml:Audience>
</saml:AudienceRestriction>
</saml:Conditions>
<saml:AuthnStatement AuthnInstant="2023-12-28T14:58:28.132Z" SessionIndex="68730d44-5ba7-4b22-bcc5-119a759df47d">
<saml:AuthnContext>
<saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport</saml:AuthnContextClassRef>
</saml:AuthnContext>
</saml:AuthnStatement>
<saml:AttributeStatement>
<saml:Attribute FriendlyName="uid" Name="urn:oid:0.9.2342.19200300.100.1.1" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">
<saml:AttributeValue xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:string">68814fab-4bff-4a47-98ca-63fbee6a3f5a</saml:AttributeValue>
</saml:Attribute>
<saml:Attribute FriendlyName="eduPersonPrincipalName" Name="urn:oid:1.3.6.1.4.1.5923.1.1.1.6" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
<saml:AttributeValue xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:string">[email protected]</saml:AttributeValue>
</saml:Attribute>
<saml:Attribute FriendlyName="email" Name="urn:oid:0.9.2342.19200300.100.1.3" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
<saml:AttributeValue xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:string">[email protected]</saml:AttributeValue>
</saml:Attribute>
<saml:Attribute FriendlyName="sn" Name="urn:oid:2.5.4.4" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
<saml:AttributeValue xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:string">Peach</saml:AttributeValue>
</saml:Attribute>
<saml:Attribute FriendlyName="givenName" Name="urn:oid:2.5.4.42" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
<saml:AttributeValue xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:string">Brenda J</saml:AttributeValue>
</saml:Attribute>
</saml:AttributeStatement>
</saml:Assertion>
</samlp:Response>
This is the assertion consumer service url being called from the idp:
https://*****-uat.*****/SAML/ACS?binding=urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST
This is the exception that is generated
Microsoft.IdentityModel.Tokens.Saml2.Saml2SecurityTokenReadException: IDX13102: Exception thrown while reading 'AuthnContext' for Saml2SecurityToken.
---> System.ArgumentNullException: IDX10000: The parameter 'value' cannot be a 'null' or an empty object. (Parameter 'value')
at Microsoft.IdentityModel.Tokens.Saml2.Saml2AuthenticationContext.set_ClassReference(Uri value)
at Microsoft.IdentityModel.Tokens.Saml2.Saml2AuthenticationContext..ctor(Uri classReference, Uri declarationReference)
at ITfoxtec.Identity.Saml2.Tokens.Saml2TokenSerializer.ReadAuthenticationContext(XmlDictionaryReader reader)
--- End of inner exception stack trace ---
at ITfoxtec.Identity.Saml2.Tokens.Saml2TokenSerializer.ReadAuthenticationContext(XmlDictionaryReader reader)
at Microsoft.IdentityModel.Tokens.Saml2.Saml2Serializer.ReadAuthenticationStatement(XmlDictionaryReader reader)
at Microsoft.IdentityModel.Tokens.Saml2.Saml2Serializer.ReadAssertion(XmlReader reader)
at Microsoft.IdentityModel.Tokens.Saml2.Saml2SecurityTokenHandler.ReadSaml2Token(String token)
at ITfoxtec.Identity.Saml2.Saml2AuthnResponse.ReadSecurityToken(String tokenString)
at ITfoxtec.Identity.Saml2.Saml2AuthnResponse.Read(String xml, Boolean validate, Boolean detectReplayedTokens)
at ITfoxtec.Identity.Saml2.Saml2PostBinding.Read(HttpRequest request, Saml2Request saml2RequestResponse, String messageName, Boolean validate, Boolean detectReplayedTokens)
at ITfoxtec.Identity.Saml2.Saml2Binding.ReadSamlResponse(HttpRequest request, Saml2Response saml2Response)
at Farmland.Controllers.AuthController.AssertionConsumerService() in C:\Users\cjohnson\Source\repos\ct1-agriculture-comparable-land-sales\ComparableLandSales\Farmland\Controllers\AuthController.cs:line 67
at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.TaskOfIActionResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeActionMethodAsync>g__Awaited|12_0(ControllerActionInvoker invoker, ValueTask`1 actionResultValueTask)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeNextActionFilterAsync>g__Awaited|10_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Rethrow(ActionExecutedContextSealed context)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeInnerFilterAsync()
--- End of stack trace from previous location where exception was thrown ---
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextResourceFilter>g__Awaited|24_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResourceExecutedContextSealed context)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.InvokeFilterPipelineAsync()
--- End of stack trace from previous location where exception was thrown ---
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope)
at Microsoft.AspNetCore.Routing.EndpointMiddleware.<Invoke>g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger)
at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
at Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware.<Invoke>g__Awaited|6_0(ExceptionHandlerMiddleware middleware, HttpContext context, Task task)
This error should be resolved with the latest release 4.10.7.
** EDITED **
The AuthnStatement/AuthnContext/AuthnContextClassRef
is apparently not supported in .NET Core 3.1, I didn't know that.
The packaged is based on .NET and you must therefore update to a newer .NET version I'm afraid.