lightSAML / SpBundle

SAML2 SP Symfony Bundle based on LightSAML
https://www.lightsaml.com/SP-Bundle/
MIT License
66 stars 70 forks source link

Symfony 2.8 security setup with LightSamlPhp Bundle - Multiple Login Methods #25

Closed Jaswinder00 closed 7 years ago

Jaswinder00 commented 8 years ago

Hi, I am running into an issue with setting up Authentication in Symfony 2.8 with Saml plugin (https://www.lightsaml.com/SP-Bundle/Getting-started/). Problem: I want to able to login via SAML and via going to admin page. The /admin/login page works fine, I see the user authenticated from the database. However, when I try to go through the Saml process, I always land on the /discovery page. When I see the logs, I do see the user is authenticated, but the page is redirected to discovery page. So, I think I have something not correctly configured in the security settings. Please let me know if you can help.

Here are the settings from config/security.yml file:

firewalls:
        dev:
            pattern: ^/(_(profiler|wdt)|css|images|js)/
            security: false

        login_firewall:
            pattern: ^/saml/login$
            anonymous: ~
        discovery_firewall:
            pattern: ^/saml/discovery$
            anonymous: ~

        secured_area:
            pattern:   ^/
            anonymous: ~
            light_saml_sp:
                provider: db_provider    # user provider name configured in step 9
                #user_creator: user_creator  # name of the user creator service created in step 10
                login_path: /saml/login
                check_path: /saml/login_check
                default_target_path: /profile

            form_login:
                login_path: /admin/login
                check_path: /admin/login_check
                default_target_path: /
                remember_me: true
            logout:
                path:   /logout
                target: /

            # activate different ways to authenticate

            # http_basic: ~
            # http://symfony.com/doc/current/book/security.html#a-configuring-how-your-users-will-authenticate

            # form_login: ~
            # http://symfony.com/doc/current/cookbook/security/form_login_setup.html

    access_control:
        - { path: ^/, roles: IS_AUTHENTICATED_ANONYMOUSLY }
        - { path: ^/login$, roles: IS_AUTHENTICATED_ANONYMOUSLY }
        - { path: ^/admin/login$, roles: IS_AUTHENTICATED_ANONYMOUSLY }        
        - { path: ^/profile, roles: ROLE_USER }
tmilos commented 8 years ago

You don't need separate firewall for saml login and discovery. It must be within fw lightsam is configured in order to work.

I would suggest you first to trough all getting started steps and once that works you add form listener to the same fw using the same user provider

Jaswinder00 commented 8 years ago

Thanks for responding! If I remove the fw for login and discovery, I get redirected back to /saml/login page with the same error indicating net::ERR_TOO_MANY_REDIRECTS. I followed the directions to setup, but something is lacking. Any other thoughts?

tmilos commented 8 years ago

Not sure what you see as a problem. Starting saml auth instead of form auth? Which listener you want to respond first? Try reordering listeners or define an entry point.

Though... if you wish saml first, and complaining about it not working, then there is a case of too many redirects if saml listener does not authenticate a user from #19. If that's the case, until it's fixed you can create user creator to return dummy/non-persisted user w/out roles

Please explain the use cases you need, and why you need two login methods

Jaswinder00 commented 8 years ago

The requirement is to login to the system using two methods: Saml and Direct (for example admin). I want Saml firewall to be first. I do not have the UserCreator class as it is optional. We may not want users to be created as of you. Just know that the logs show that user is authenticated but it goes to infinite loop. Here are the error logs if this helps:

[2016-08-10 11:32:53] app.DEBUG: Known issuer resolved: "http://idp.haven.com/simplesaml/saml2/idp/metadata.php" {"profile_id":"sso_sp_receive_response","own_role":"sp","action":"LightSaml\\Action\\Profile\\Inbound\\Message\\ResolvePartyEntityIdAction","top_context_id":"000000003c9fd4e400000000802115ad","partyEntityId":"http://idp.haven.com/simplesaml/saml2/idp/metadata.php"} [][2016-08-10 11:32:53] app.DEBUG: Message signature validated with key "/C=US/ST=Maryland/L=Bethesda/O=CompanyName/OU=Healthcare/CN=HAVENIDP/emailAddress=testuser@gmail.com, /C=US/ST=Maryland/L=Bethesda/O=CompanyName/OU=Healthcare/CN=HAVENIDP/emailAddress=testuser@gmail.com" {"profile_id":"sso_sp_receive_response","own_role":"sp","action":"LightSaml\\Action\\Profile\\Inbound\\Message\\MessageSignatureValidatorAction","top_context_id":"000000003c9fd4e400000000802115ad","credential":"[object] (LightSaml\\Credential\\X509Credential: {})"} []
[2016-08-10 11:32:53] app.DEBUG: Response has no encrypted assertions {"profile_id":"sso_sp_receive_response","own_role":"sp","action":"LightSaml\\Action\\Profile\\Inbound\\Response\\DecryptAssertionsAction","top_context_id":"000000003c9fd4e400000000802115ad"} []
[2016-08-10 11:32:53] app.DEBUG: Known assertion issuer: "http://idp.haven.com/simplesaml/saml2/idp/metadata.php" {"profile_id":"sso_sp_receive_response","own_role":"sp","action":"LightSaml\\Action\\Assertion\\Inbound\\KnownAssertionIssuerAction","top_context_id":"000000003c9fd4e400000000802115ad"} []
[2016-08-10 11:32:53] doctrine.DEBUG: SELECT t0.assertion_id AS assertion_id_1, t0.entity_id AS entity_id_2, t0.expiry_time AS expiry_time_3, t0.id AS id_4 FROM id_entry t0 WHERE t0.entity_id = ? AND t0.assertion_id = ? ["http://idp.haven.com/simpl [...]","_44a9bb104749246a8648fdf7c [...]"] []
[2016-08-10 11:32:53] doctrine.DEBUG: SELECT t0.assertion_id AS assertion_id_1, t0.entity_id AS entity_id_2, t0.expiry_time AS expiry_time_3, t0.id AS id_4 FROM id_entry t0 WHERE t0.entity_id = ? AND t0.assertion_id = ? ["http://idp.haven.com/simpl [...]","_44a9bb104749246a8648fdf7c [...]"] []
[2016-08-10 11:32:53] doctrine.DEBUG: "START TRANSACTION" [] []
[2016-08-10 11:32:53] doctrine.DEBUG: INSERT INTO id_entry (assertion_id, entity_id, expiry_time) VALUES (?, ?, ?) {"1":"_44a9bb104749246a8648fdf7c [...]","2":"http://idp.haven.com/simpl [...]","3":"2016-08-10 15:37:52"} []
[2016-08-10 11:32:53] doctrine.DEBUG: "COMMIT" [] []
[2016-08-10 11:32:53] app.DEBUG: Assertion signature validated with key "/C=US/ST=Maryland/L=Bethesda/O=CompanyName/OU=Healthcare/CN=HAVENIDP/emailAddress=testuser@gmail.com, /C=US/ST=Maryland/L=Bethesda/O=CompanyName/OU=Healthcare/CN=HAVENIDP/emailAddress=testuser@gmail.com" {"profile_id":"sso_sp_receive_response","own_role":"sp","action":"LightSaml\\Action\\Assertion\\Inbound\\AssertionSignatureValidatorAction","top_context_id":"000000003c9fd4e400000000802115ad","credential":"[object] (LightSaml\\Credential\\X509Credential: {})"} []
[2016-08-10 11:32:53] app.DEBUG: Removed request state "_d7f76b33d97bb001769b3f0de2980383587137af0d" {"profile_id":"sso_sp_receive_response","own_role":"sp","action":"LightSaml\\Action\\Profile\\FlushRequestStatesAction","top_context_id":"000000003c9fd4e400000000802115ad"} []
[2016-08-10 11:32:53] app.DEBUG: Request state "_d7f76b33d97bb001769b3f0de2980383587137af0d" does not exist {"profile_id":"sso_sp_receive_response","own_role":"sp","action":"LightSaml\\Action\\Profile\\FlushRequestStatesAction","top_context_id":"000000003c9fd4e400000000802115ad"} []
[2016-08-10 11:32:53] doctrine.DEBUG: SELECT t0.email AS email_1, t0.password AS password_2, t0.firstname AS firstname_3, t0.lastname AS lastname_4, t0.isactive AS isactive_5, t0.created_timestamp AS created_timestamp_6, t0.id AS id_7, t0.role_id AS role_id_8 FROM user t0 WHERE t0.email = ? LIMIT 1 ["samluser@gmail.com"] []
[2016-08-10 11:32:53] security.INFO: User has been authenticated successfully. {"username":"samluser@gmail.com"} []
[2016-08-10 11:32:53] security.DEBUG: Stored the security token in the session. {"key":"_security_secured_area"} []
[2016-08-10 11:32:53] request.INFO: Matched route "dc_gov_haven_admin". {"route_parameters":{"_controller":"DCGov\\HavenBundle\\Controller\\DefaultController::indexAction","_route":"dc_gov_haven_admin"},"request_uri":"http://dev.haven.com/app_dev.php/profile"} []
[2016-08-10 11:32:53] security.DEBUG: Read existing security token from the session. {"key":"_security_secured_area"} []
[2016-08-10 11:32:53] doctrine.DEBUG: SELECT t0.email AS email_1, t0.password AS password_2, t0.firstname AS firstname_3, t0.lastname AS lastname_4, t0.isactive AS isactive_5, t0.created_timestamp AS created_timestamp_6, t0.id AS id_7, t0.role_id AS role_id_8 FROM user t0 WHERE t0.id = ? [3] []
[2016-08-10 11:32:53] security.DEBUG: User was reloaded from a user provider. {"username":"samluser@gmail.com","provider":"Symfony\\Bridge\\Doctrine\\Security\\User\\EntityUserProvider"} []
[2016-08-10 11:32:53] security.INFO: An AuthenticationException was thrown; redirecting to authentication entry point. {"exception":"[object] (Symfony\\Component\\Security\\Core\\Exception\\ProviderNotFoundException(code: 0): No Authentication Provider found for token of class \"LightSaml\\SpBundle\\Security\\Authentication\\Token\\SamlSpToken\". at /var/www/safehaven/app/cache/dev/classes.php:2728)"} []
[2016-08-10 11:32:53] security.DEBUG: Calling Authentication entry point. [] []
[2016-08-10 11:32:53] security.DEBUG: Stored the security token in the session. {"key":"_security_secured_area"} []
[2016-08-10 11:32:53] request.INFO: Matched route "lightsaml_sp.login". {"route_parameters":{"_controller":"LightSaml\\SpBundle\\Controller\\DefaultController::loginAction","_route":"lightsaml_sp.login"},"request_uri":"http://dev.haven.com/app_dev.php/saml/login"} []
[2016-08-10 11:32:53] security.DEBUG: Read existing security token from the session. {"key":"_security_secured_area"} []
[2016-08-10 11:32:53] doctrine.DEBUG: SELECT t0.email AS email_1, t0.password AS password_2, t0.firstname AS firstname_3, t0.lastname AS lastname_4, t0.isactive AS isactive_5, t0.created_timestamp AS created_timestamp_6, t0.id AS id_7, t0.role_id AS role_id_8 FROM user t0 WHERE t0.id = ? [3] []
[2016-08-10 11:32:53] security.DEBUG: User was reloaded from a user provider. {"username":"samluser@gmail.com","provider":"Symfony\\Bridge\\Doctrine\\Security\\User\\EntityUserProvider"} []
Jaswinder00 commented 8 years ago

Any thoughts on the next steps or things that I can try?

tmilos commented 8 years ago

security config is not whole... providers are missing

You didn't document what is dc_gov_haven_admin route and which firewall it belongs to

Logs says on 2016-08-10 11:32:53 user was reloaded from Symfony\Bridge\Doctrine\Security\User\EntityUserProvider and AuthenticationException was thrown - No Authentication Provider found for token of class \"LightSaml\SpBundle\Security\Authentication\Token\SamlSpToken

So, it seems, during authentication saml listener creates SamlSpToken but security on dc_gov_haven_admin route seems not to include saml listener to recognize that token.

Put code in between 3 backticks - it's more readable

Jaswinder00 commented 8 years ago

The dc_gov_haven_admin route simply points to the home page. here is how I have it defined in the routing.yml file:

dc_gov_haven_homepage: path: / defaults: { _controller: "DCGovHavenBundle:Default:index" }

I am not sure if I understand your comment. How do I include the saml listener into dc_gov_haven_admin route security?

tmilos commented 8 years ago

Firewalls react only on specific url pattern. In each firewall different security listeners are configured. Depending on the route url, different firewall will be chosen, hence different security listeners will react.

Now I saw, in the log trace, the route dc_gov_haven_admin is /profile and not / as you say.... which is dc_gov_haven_homepage.

Error message that there were no Authentication Provider for SamlSpToken on url /profile means that url pattern does not fit a firewall where light_saml_sp listener was configured.

Paste your whole config/security.yml file as it was when you got that log trace with exception.

Jaswinder00 commented 8 years ago

I have changed the target route to be / for testing and did not work. Here is the full security.yml file

security:
    encoders:
        Symfony\Component\Security\Core\User\User: plaintext
        DCGov\HavenBundle\Entity\User:
            algorithm: bcrypt
            cost: 12

    role_hierarchy:
        ROLE_ADMIN:       ROLE_USER
        ROLE_SUPER_ADMIN: [ROLE_USER, ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH]

    # http://symfony.com/doc/current/book/security.html#where-do-users-come-from-user-providers
    providers:
        chain_provider:
            chain:
                providers: [db_provider,in_memory]
        db_provider:
            entity:
                class: DCGov\HavenBundle\Entity\User
                property: email

        in_memory:
            memory:
                users:
                    jsingh@gmail.com:
                        password: pa$$123
                        roles: 'ROLE_ADMIN'

    firewalls:
        # disables authentication for assets and the profiler, adapt it according to your needs
        dev:
            pattern: ^/(_(profiler|wdt)|css|images|js)/
            security: false

        secured_area:
            pattern:   ^/
            anonymous: ~
            light_saml_sp:
                provider: db_provider    # user provider name configured in step 9
                user_creator: user_creator  # name of the user creator service created in step 10
                login_path: /saml/login
                check_path: /saml/login_check
                default_target_path: /

            form_login:
                login_path: /admin/login
                check_path: /admin/login_check
                default_target_path: /
                remember_me: true
            logout:
                path:   /logout
                target: /

    access_control:
        - { path: ^/, roles: IS_AUTHENTICATED_ANONYMOUSLY }
        - { path: ^/saml/login$, roles: IS_AUTHENTICATED_ANONYMOUSLY }
        - { path: ^/admin/login$, roles: IS_AUTHENTICATED_ANONYMOUSLY }        
        - { path: ^/profile, roles: ROLE_USER }
tmilos commented 8 years ago

It looks fine... one firewall on /. I'm not sure why you're getting at AuthenticationProviderManager::authenticate() on already authenticated token on the / route, when it's not an authentication route neither for saml nor form listener.

You could place brakepoints on app/cache/dev/classes.php line foreach ($this->providers as $provider) { few lines above your line 2728 where that exception is throws an see which providers are called, and also check the stack trace to see which listener called authentication at the first place.

Then next brakepoint on Symfony\Component\Security\Http\Firewall::onKernelRequest() on the foreach ($listeners as $listener) { line and see which listeners are used.

In any case I have modified the DemoSP app to have two login methods just like in your case, at it works fine. Inspect that code and compare to yours... there must be some difference you're missing.

Jaswinder00 commented 8 years ago

Thanks for looking into this! I have tried debugging the code, comparing the DemoSP app that you have put up. I also compared the SP-Bundle code implementation with Symfony Custom Providers (http://symfony.com/doc/current/security/custom_authentication_provider.html). So far, no luck. One thing I also notice that token is that the SamlSP is not authenticated from Symfony profile. I will continue with debugging this code. Meanwhile, if you have other suggestions, please let me know.

Jaswinder00 commented 8 years ago

So, I have finally able to resolve this issue. Basically, if the user exists but it is not authenticated, it creates a constant loop between homepage and login page when using saml. I resolved this issue by implementing the EquatableInterface interface in the User class. Please indicate the same in your docs as it may help someone running into this issue. Thanks for your help!

INSEAD-asim commented 8 years ago

@Jaswinder00, you have the similar issue we had earlier. Actually, when IDP authenticate and user fail to authorize in SP (your app), it keep looping between IDP and SP. Because IDP inform your credential are valid but SP reject as user not found for authorization. I solved this issue in my app by authorizing these users without any role OR dummy role. This will let SP throws error 403 not authorized.

Jaswinder00 commented 8 years ago

@INSEAD-asim I have not tested the solution that you mentioned, but we need roles to authorize the users. The issue I was having with loop within the Symfony routes after the user was authenticated by both IDP and SP. But, thanks for your response.

tmilos commented 7 years ago

@Jaswinder00 have you managed to make it work? Can we close the issue or there are some new insights?

Jaswinder00 commented 7 years ago

Yes. I got it resolved. Please close this issue.

On Tuesday, September 20, 2016, Milos Tomic notifications@github.com wrote:

@Jaswinder00 https://github.com/Jaswinder00 have you managed to make it work? Can we close the issue or there are some new insights?

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/lightSAML/SpBundle/issues/25#issuecomment-248222867, or mute the thread https://github.com/notifications/unsubscribe-auth/ABpO7sUHVm1sfs3gDN8TMyPmvAb52lNdks5qr4h7gaJpZM4JgDfd .