Search code examples
.net-coresaml-2.0itfoxtec-identity-saml2

itfoxtec-identity-saml2 Assertion Consumer Service Exception


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 #

enter image description here

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)

Solution

  • 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.