Sustainsys / Saml2

Saml2 Authentication services for ASP.NET
Other
952 stars 600 forks source link

ADFS initiated logout does not work when have multiple AuthServices instances for different Idps #515

Closed kb99 closed 2 years ago

kb99 commented 8 years ago

We are using AuthServices to provide SSO integration for a number of different IDPs.

We are following a similar pattern as described in the bottom of the AuthServices-Okta Setup Guide where we create a new instance of AuthServices middle-ware for each Idp that we want to integrate with.

We have successfully integrated with a Okta instance and everything is working as expected. We are now testing an integration with ADFS.

When there was only one AuthServices instance running for the ADFS Idp everything works as expected including both login flows (SP initialted and Idp Initialted and also Idp Initiated logout). Everything also worked as expected when there were two AuthServices instances loaded (1 for ADFS, 1 for Okta) - when the ADFS instance was loaded first.

However, if I change the order that the instances are loaded so that the ADFS instance is loaded second then an error is generated during the Idp Initiated logout request.

Here is the SAML document that is sent to the ADFS AuthServices instance

GET https://localhost:44300/identity/0ec7bddd11d848f2a308acc6adb87259/Logout?SAMLRequest=...

<samlp:LogoutRequest ID="_5a0fab0b-c815-484e-83ad-f477ea59f188"
                     Version="2.0"
                     IssueInstant="2016-06-29T10:54:53.518Z"
                     Destination="https://localhost:44300/identity/0ec7bddd11d848f2a308acc6adb87259/Logout"
                     Consent="urn:oasis:names:tc:SAML:2.0:consent:unspecified"
                     NotOnOrAfter="2016-06-29T10:59:53.518Z"
                     xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
                     >
    <Issuer xmlns="urn:oasis:names:tc:SAML:2.0:assertion">http://adfs2.abcuat.local/adfs/services/trust</Issuer>
    <NameID xmlns="urn:oasis:names:tc:SAML:2.0:assertion">Administrator@mail.uat.abc.com</NameID>
    <samlp:SessionIndex>_53268e6f-a602-4300-88a9-046c5d367e7d</samlp:SessionIndex>
    <samlp:SessionIndex>_c0134377-c15a-49f8-926a-efedcb0a10f9</samlp:SessionIndex>
</samlp:LogoutRequest>

The error message received is

Cannot verify signature of message from unknown sender http://adfs2.abcuat.local/adfs/services/trust.

Stack trace is

[InvalidSignatureException: Cannot verify signature of message from unknown sender http://adfs2.abcuat.local/adfs/services/trust.]
   Kentor.AuthServices.WebSso.Saml2RedirectBinding.GetTrustLevel(XmlElement documentElement, HttpRequestData request, IOptions options) in C:\Users\kevin\Downloads\authservices-master (1)\authservices-master\Kentor.AuthServices\WebSso\Saml2RedirectBinding.cs:132
   Kentor.AuthServices.WebSso.Saml2RedirectBinding.Unbind(HttpRequestData request, IOptions options) in C:\Users\kevin\Downloads\authservices-master (1)\authservices-master\Kentor.AuthServices\WebSso\Saml2RedirectBinding.cs:98
   Kentor.AuthServices.WebSso.LogoutCommand.Run(HttpRequestData request, String returnPath, IOptions options) in C:\Users\kevin\Downloads\authservices-master (1)\authservices-master\Kentor.AuthServices\WebSso\LogoutCommand.cs:72
   Kentor.AuthServices.Owin.<ApplyResponseGrantAsync>d__2.MoveNext() in C:\Users\kevin\Downloads\authservices-master (1)\authservices-master\Kentor.AuthServices.Owin\KentorAuthServicesAuthenticationHandler.cs:111
   System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) +92

I've downloaded the source code and tried to step through the code and the error is occurring in the GetTrustLevel() method but the IdentityProviders dictionary contains the Okta IdentityProvider and not the ADFS IdentityProvider. The code does successfully work when when the ADFS IdentityProvider is loaded in the dictionary. So it appears that the code is running for both authServices instances and failing on the one that doesn't have the ADFS identityprovider loaded.

In our test case that is failing, we have two AuthServices Instances running - here are the 2 endpoints

https://localhost:44300/identity/c6ca0e1cdc644940884339ac118ee2d5
https://localhost:44300/identity/0ec7bddd11d848f2a308acc6adb87259

I would have thought that the Logout request would have got handled on the specific AuthServices instance that the request was sent to - ie. https://localhost:44300/identity/0ec7bddd11d848f2a308acc6adb87259/Logout?

Any thoughts on what might be the issue?

AndersAbel commented 8 years ago

Thanks for the clear error description.

Looks like you're having two different modulePath that's good. It's the most common error for multiple instance setups.

I have an idea of what might be happening:

  1. ADFS sends LogoutRequest, addressed at ADFS AuthServices instance.
  2. ADFS AuthServices instance receives, and validates the LogoutRequest.
  3. ADFS AuthServices instances returns a redirect back to ADFS, and sets an AuthenticateRevoke in the AuthenticationManager to make the cookie middleware remove the auth cookie.
  4. OKTA AuthServices instance detects the AuthenticateRevoke and calls LogoutCommand to initiate a logout.
  5. OKTA AuthServices LogoutCommand detects the SAMLRequest and tries to validate the response.

Do you have time to debug and see if my guess is right? In that case some more checks should be added in the AuthServices code to prevent this.

kb99 commented 8 years ago

Thanks fro your quick reply - I'll debug locally and see if your guess is right.

kb99 commented 8 years ago

I set a break-point in the Kentor.AuthServices.WebSso.Saml2RedirectBinding.GetTrustLevel() method to check what Idp is present when it tries to retrieve the Idp based on the Issuer. When the Logout request is received from the Idp, the breakpoint is hit and the correct Idp is present so the method is able to complete successfully.

I can see in the Kentor.AuthServices.Owin.KentorAuthServicesAuthenticationHandler.ApplyResponseGrantAsync(). method that a response is generated which sets the redirectUrl to be https://adfs2.dabcuat.local/adfs/ls/?SAMLResponse=...

<LogoutResponse ID="_c5b7dec7-f119-453c-8eb6-744a826367db" Version="2.0" IssueInstant="2016-06-29T12:50:56Z" InResponseTo="_db6ddbf0-c88a-4feb-a2b8-591ac46eb410" Destination="https://adfs2.abcuat.local/adfs/ls/" xmlns="urn:oasis:names:tc:SAML:2.0:protocol">
    <Issuer xmlns="urn:oasis:names:tc:SAML:2.0:assertion">https://localhost:44300/identity/0ec7bddd11d848f2a308acc6adb87259</Issuer>
    <Status>
        <StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success" />
    </Status>
</LogoutResponse>

From here it continues on and calls CommandResultExtensions.Apply() which in turn calls context.Authentication.SignOut();

If I let the code run, the breakpoint in Kentor.AuthServices.Owin.KentorAuthServicesAuthenticationHandler.ApplyResponseGrantAsync(). gets called again but this time the Options variable is for the for the Okta AuthServices instance. The line var revoke = Helper.LookupSignOut(Options.AuthenticationType, Options.AuthenticationMode); does return a revoke object so it does initiate a logout.

So it does appear that your guess is right.

Is there anything that can be done in the meantime to try and prevent this from happening while a fix is implemented?

kb99 commented 8 years ago

Is there a way maybe that I could disable handling the IDP Initiated Logout requests for the moment? We could work with just the Login functionality for the moment and then reenable support for IDP Initialted Logout down the road?

AndersAbel commented 8 years ago

Set the mode of the owin middleware to passive (it's a property in the KentorAuthServicesAuthenticationOptions class). That should stop the second middleware from interfering.

But it also means that you need to explicitly state the authentication type to sign out from when doing an SP-initiated sign out. If you just go with no auth type, the AuthServices middleware won't trigger a federated logout.

kb99 commented 8 years ago

Yes that change does fix the issue and it is now working in my local environment.

In relation to your second point - the user will still get signed out of the application but no SAML signout request will be sent to the Idp - is that correct? We currently have DisableOutboundLogoutRequests set to true when initializing the Idp so this is not a major issue for us at the moment.

AndersAbel commented 8 years ago

In relation to your second point - the user will still get signed out of the application but no SAML signout request will be sent to the Idp - is that correct? Yes, that is correct.

We currently have DisableOutboundLogoutRequests set to true when initializing the Idp so this is not a major issue for us at the moment. Then this has no effect and you won't have to change how you initiate the logout.

I'm labelling this as a bug as it need to be addressed in the AuthServices code. Most likely change is to have the owin middleware not call LogoutCommand.Run when an AuthenticationGrantRevoke is detected, but rather check if the current identity is from an idp of the AuthServices instances and in that case call LogoutCommand.InitiateLogout.

LukeeK commented 7 years ago

Hi, I am not sure if this the right place to right but I think my issue is related. In my scenario I also have two idps, in test environment I am using our test adfs and http://stubidp.kentor.se as second idp. I am adding saml authentication to web form app with using owin. To login I am using links =>"./Internal/SignIn?idp=..." and "./Externall/SignIn?idp=...". To logout I am calling context.Authentication.SignOut()

I am having problem with logging out. Only for the first decalred idp remote logout request is performed, for the second only local. When switch the order of declaration result is also reversed. I can put my config if needed. Any info on that ?

AndersAbel commented 7 years ago

@LukeeK That looks like a different issue, let's handle it in #749.