Sustainsys / Saml2

Saml2 Authentication services for ASP.NET
Other
961 stars 603 forks source link

Multiple SSO providers causing error #1426

Closed Gillardo closed 10 months ago

Gillardo commented 10 months ago

I have setup 2 enterprise applications in Azure. And if i implement these separately using this library, they both work fine. If however, i implement both of them on 1 website, the site builds and runs, but upon trying the second SSO provider, i get an error The signature verified correctly with the key contained in the signature, but that key is not trusted., the stack trace is like this

Source  Sustainsys.Saml2
   at Sustainsys.Saml2.XmlHelpers.VerifySignature(IEnumerable`1 signingKeys, SignedXml signedXml, XmlElement signatureElement, Boolean validateCertificate)
   at Sustainsys.Saml2.XmlHelpers.IsSignedByAny(XmlElement xmlElement, IEnumerable`1 signingKeys, Boolean validateCertificate, String minimumSigningAlgorithm)
   at Sustainsys.Saml2.Saml2P.Saml2Response.<>c__DisplayClass60_0.<ValidateSignature>b__0(XmlElement a)
   at System.Linq.Enumerable.Any[TSource](IEnumerable`1 source, Func`2 predicate)
   at Sustainsys.Saml2.Saml2P.Saml2Response.ValidateSignature(IOptions options, IdentityProvider idp)
   at Sustainsys.Saml2.Saml2P.Saml2Response.Validate(IOptions options, IdentityProvider idp)
   at Sustainsys.Saml2.Saml2P.Saml2Response.CreateClaims(IOptions options, IdentityProvider idp)+MoveNext()
   at System.Collections.Generic.List`1..ctor(IEnumerable`1 collection)
   at System.Linq.Enumerable.ToList[TSource](IEnumerable`1 source)
   at Sustainsys.Saml2.Saml2P.Saml2Response.GetClaims(IOptions options, IDictionary`2 relayData)
   at Sustainsys.Saml2.WebSso.AcsCommand.ProcessResponse(IOptions options, Saml2Response samlResponse, StoredRequestState storedRequestState, IdentityProvider identityProvider, String relayState)
   at Sustainsys.Saml2.WebSso.AcsCommand.Run(HttpRequestData request, IOptions options)
   at Sustainsys.Saml2.AspNetCore2.Saml2Handler.HandleRequestAsync()
   at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)

My setup is this (which as i said, if i comment one of these out, works perfectly)

// add m2i SAML provider
authenticationBuilder.AddSaml2("ABC" + Saml2Defaults.Scheme, options =>
{
    // organization
    var organization = new Organization();
    organization.Names.Add(new LocalizedName("XXX", "en-GB"));
    organization.DisplayNames.Add(new LocalizedName("XXX", "en-GB"));
    organization.Urls.Add(new LocalizedUri(new Uri("https://www.XXX.co.uk"), "en-GB"));

    // service provider
    options.SPOptions.Organization = organization;
    options.SPOptions.EntityId = new EntityId("https://www.ABC.co.uk");
    options.SPOptions.WantAssertionsSigned = true;
    options.SPOptions.MinIncomingSigningAlgorithm = "http://www.w3.org/2000/09/xmldsig#rsa-sha1";
    options.SPOptions.ServiceCertificates.Add(signingCert);

    // because we are behind a load balancer, we need to set this to it doesnt go back to http
    options.SPOptions.PublicOrigin = new Uri("http://www.MY-URL.com");

    var idp = new Sustainsys.Saml2.IdentityProvider(new EntityId("https://sts.windows.net/123-456-789/"), options.SPOptions)
    {
        MetadataLocation = Path.Combine(Directory.GetCurrentDirectory(), "App_Data", "saml-m2i.xml"),
        LoadMetadata = true
    };

    // only used for Azure SAML setup
    idp.SingleSignOnServiceUrl = new Uri("https://login.microsoftonline.com/123-456-789/saml2");

    options.IdentityProviders.Add(idp);
});

// check if SAML used, moved this code to here so you can see why authenticationBuilder is needed
if (appSettings.Authentication != null && appSettings.Authentication.Saml != null)
{
    authenticationBuilder.AddSaml2(Saml2Defaults.Scheme, options =>
    {
        // organization
        var organization = new Organization();
        organization.Names.Add(new LocalizedName("XXX", "en-GB"));
        organization.DisplayNames.Add(new LocalizedName("XXX", "en-GB"));
        organization.Urls.Add(new LocalizedUri(new Uri("https://www.XXX.co.uk"), "en-GB"));

        // service provider
        options.SPOptions.Organization = organization;
        options.SPOptions.EntityId = new EntityId("http://www.XYZ.co.uk");
        options.SPOptions.WantAssertionsSigned = true;
        options.SPOptions.MinIncomingSigningAlgorithm = "http://www.w3.org/2000/09/xmldsig#rsa-sha1";
        options.SPOptions.ServiceCertificates.Add(signingCert);

        // because we are behind a load balancer, we need to set this to it doesnt go back to http
        options.SPOptions.PublicOrigin = new Uri("http://www.MY-URL.com");

        var idp = new Sustainsys.Saml2.IdentityProvider(new EntityId(appSettings.Authentication.Saml.ProviderKey), options.SPOptions)
        {
            MetadataLocation = Path.Combine(Directory.GetCurrentDirectory(), "App_Data", "saml-metadata.xml"),
            LoadMetadata = true
        };

        // only used for Azure SAML setup
        idp.SingleSignOnServiceUrl = new Uri(appSettings.Authentication.Saml.LoginUrl);

        options.IdentityProviders.Add(idp);
    });
}

Any reason why this would be a problem and why would i get this message?

Gillardo commented 10 months ago

I do notice that on Azure Enterprise apps, the LoginURL "ID" that is used is the same for all apps, could it be this that is causing an issue?

image

image

AndersAbel commented 10 months ago

Each instance of AddSaml2 must have a unique ModulePath or the first one added will handle responses meant for the second configuration.

Gillardo commented 10 months ago

Is it this? options.SPOptions.ModulePath, and what would i need to set it to? WHat if i am using the same SAMLController for both? Do i need to split it?

AndersAbel commented 10 months ago

It's the options.SPOptions.ModulePath yes. You can set it to any path yes. But please note that changing this will require an update to the configuration on the Idp (Entra Id) side as it changes the Acs Url.

Gillardo commented 10 months ago

Thank you this worked perfectly!!