apokalipto / devise_saml_authenticatable

Devise SAML 2.0 authentication strategy
MIT License
296 stars 154 forks source link

Does this gem support SAML single logout? #126

Closed Yu-Chieh-Henry-Yang closed 3 years ago

Yu-Chieh-Henry-Yang commented 6 years ago

I'm trying to implement SAML single logout using this gem, in a minimal devise saml rails app with user model and post model.

Does this gem support SAML single logout?

adamstegman commented 6 years ago

Yes, though the documentation is not very helpful: https://github.com/apokalipto/devise_saml_authenticatable#logout-request. That gives you the route to configure in your IdP and tells you what config setting this gem needs.

The config value should be set to an attribute on your users table that can store the IdP response's session index. For instance, we created a session_index column and used that:

Devise.setup do |config|
  # ...
  config.saml_session_index_key = :session_index
end
Yu-Chieh-Henry-Yang commented 6 years ago

Hi @adamstegman, thanks for the reply! But I still have some trouble getting my app's SAML logout setup.

I'm setting up a vanilla devise + rails + this gem app. I have created user and post models.

My routes are as follow:

$ rake routes
                   Prefix Verb     URI Pattern                        Controller#Action
        new_user_password GET      /users/password/new(.:format)      devise/passwords#new
       edit_user_password GET      /users/password/edit(.:format)     devise/passwords#edit
            user_password PATCH    /users/password(.:format)          devise/passwords#update
                          PUT      /users/password(.:format)          devise/passwords#update
                          POST     /users/password(.:format)          devise/passwords#create
 cancel_user_registration GET      /users/cancel(.:format)            devise/registrations#cancel
    new_user_registration GET      /users/sign_up(.:format)           devise/registrations#new
   edit_user_registration GET      /users/edit(.:format)              devise/registrations#edit
        user_registration PATCH    /users(.:format)                   devise/registrations#update
                          PUT      /users(.:format)                   devise/registrations#update
                          DELETE   /users(.:format)                   devise/registrations#destroy
                          POST     /users(.:format)                   devise/registrations#create
         new_user_session GET      /users/saml/sign_in(.:format)      devise/saml_sessions#new
             user_session POST     /users/saml/auth(.:format)         devise/saml_sessions#create
     destroy_user_session DELETE   /users/sign_out(.:format)          devise/saml_sessions#destroy
    metadata_user_session GET      /users/saml/metadata(.:format)     devise/saml_sessions#metadata
idp_sign_out_user_session GET|POST /users/saml/idp_sign_out(.:format) devise/saml_sessions#idp_sign_out
                    posts GET      /posts(.:format)                   posts#index
                          POST     /posts(.:format)                   posts#create
                 new_post GET      /posts/new(.:format)               posts#new
                edit_post GET      /posts/:id/edit(.:format)          posts#edit
                     post GET      /posts/:id(.:format)               posts#show
                          PATCH    /posts/:id(.:format)               posts#update
                          PUT      /posts/:id(.:format)               posts#update
                          DELETE   /posts/:id(.:format)               posts#destroy
                     root GET      /                                  posts#index

Q1: When a user wants to logout, which route should it use? I guess it's the below one. Is it correct?

     destroy_user_session DELETE   /users/sign_out(.:format)          devise/saml_sessions#destroy

Q2: What should my https://localhost:3000/saml/metadata be like? Does the below looks correct?

<md:EntityDescriptor xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata" ID="some-id-here" entityID="my-relying-party-name">
<md:SPSSODescriptor AuthnRequestsSigned="false" WantAssertionsSigned="false" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
<md:KeyDescriptor use="signing">
<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:X509Data>
<ds:X509Certificate>my-certificate-info-here</ds:X509Certificate>
</ds:X509Data>
</ds:KeyInfo>
</md:KeyDescriptor>
<md:SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="https://localhost:3000/users/sign_out" ResponseLocation="https://localhost:3000/users/sign_out"/>
<md:NameIDFormat>
urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified
</md:NameIDFormat>
<md:AssertionConsumerService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://localhost:3000/users/saml/auth" index="0" isDefault="true"/>
</md:SPSSODescriptor>
</md:EntityDescriptor>

Q3) I'm especially confused about the md:SingleLogoutService part, I assume location and response location are the address IDP calls when IDP finished signing out the user on their side? Will these two addresses ever be different? What should these 2 addresses be in this case? I set it using settings.single_logout_service_url inside devise.rb initializer and set it to "https://localhost:3000/users/sign_out" (because only it has GET/POST method?), is this correct? UPDATE: I think I should use /users/saml/idp_sign_out right. But I need to use unspecified for name id format, is there a way to turn that option on? (I specified it in my devise config but that doesn't work)

adamstegman commented 6 years ago

Q1: When a user wants to logout, which route should it use? I guess it's the below one. Is it correct?

Yes, that's correct đź‘Ť

Q2: What should my https://localhost:3000/saml/metadata be like? Does the below looks correct?

I've never dug deeply into that. Have you run into any issues with your IdP reading this metadata?

Q3) I'm especially confused about the md:SingleLogoutService part, I assume location and response location are the address IDP calls when IDP finished signing out the user on their side?

I think that's the other way around—when the user signs out from the IdP (what we call an IdP-initiated logout), that's where the IdP sends a LogoutRequest to your application. This gem handles that at the /users/idp_sign_out path.

I've never set settings.single_logout_service_url, but it looks like it's using that in the metadata. (I configured our IdP manually.) So maybe you should set that to https://localhost:3000/users/idp_sign_out.

Yu-Chieh-Henry-Yang commented 6 years ago

Have you run into any issues with your IdP reading this metadata?

Yes, for me to implement SP initiated logout, I had to use https://localhost:3000/users/saml/idp_sign_out and add a hack (https://github.com/apokalipto/devise_saml_authenticatable/issues/128), that means I use the following in my metadata: <md:SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="https://localhost:3000/users/saml/idp_sign_out" ResponseLocation="https://localhost:3000/users/saml/idp_sign_out"/>

I think that's the other way around—when the user signs out from the IdP (what we call an IdP-initiated logout), that's where the IdP sends a LogoutRequest to your application. This gem handles that at the /users/idp_sign_out path.

I've never set settings.single_logout_service_url, but it looks like it's using that in the metadata. (I configured our IdP manually.) So maybe you should set that to https://localhost:3000/users/idp_sign_out.

I think what you actually mean is not https://localhost:3000/users/idp_sign_out but https://localhost:3000/users/saml/idp_sign_out right? Because I can't see https://localhost:3000/users/idp_sign_out in rake routes

adamstegman commented 6 years ago

Yes, sorry, https://localhost:3000/users/saml/idp_sign_out is what I meant.