hwi / HWIOAuthBundle

OAuth client integration for Symfony. Supports both OAuth1.0a and OAuth2.
MIT License
2.26k stars 794 forks source link

Overriding /login/check-{service} to do some operations with the authenticated user before redirection #1408

Closed mohamedaymenkarmous closed 6 years ago

mohamedaymenkarmous commented 6 years ago

Hi everyone.

I have a problem with the HWIOauthBundle process steps. I have a website www.example.com and I registred it in Facebook application and I successfully setup HWIOauthBundle without errors.

So, since the process steps is like this (the routes prefix is /login) :

I want to do some operations in step 3 like generating a Lexik JWT Token for the authenticated user and redirect the current user to another page like https://www.example.com/oauth_redirect?access_token=[generated_token].

But I cannot did this even by using "default_target_path" in the "security.firewalls.oauth" path. Because it redirects me to the destination page without setting the queryparams (access_token) like I want.

When I check if I'm authenticated or not in another controller, I found that the current user is "anon."

PS: The symfony project hosted in www.example.com is meant to be a web api server. The users that wants to consume the REST API pages, need to authenticate using their social media accounts to register their profiles informations in the website database and then they get a Lexik JWT Token used the next time to consume the REST API from an external front-end website (www.front-end.com).

But If the user authenticates using their social media without generating the Lexik JWT Token, he will be always anonymous.

Another strange thing is, before migration from Symfony3.3 to Symfony4, for the first authentication, I get the same problem. But since the second authentication in the step 2, redirect_uri=https://www.example.com/login/service/facebook (not https://www.example.com/login/check-facebook) which is managed by the "ConnectController::connectServiceAction" controller, so I can easily do what I want and redirect the authenticated user with its Lexik JWT Token like a charm.

So the problem is around finding a way to do some operations in the /login/check-{service} and then redirecting it to another page with a specific query parameters (using GET method).

I used the official tutorial and also this tutorial : https://gist.github.com/danvbe/4476697

This problem exists in Symfony3.3 and also after the migration to Symfony4

And I think it's impossible to override the controller of the /login/check-{service} route which does not exist since it's managed by the firewall.

I'm sharing the security.yaml configuration file:

security:
    encoders:
        Symfony\Component\Security\Core\User\User : plaintext
        FOS\UserBundle\Model\UserInterface: bcrypt

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

    # https://symfony.com/doc/current/security.html#where-do-users-come-from-user-providers
    providers:
        # fosuserbundle provider
        fos_userbundle:
            id: fos_user.user_provider.username_email
        # local registered users extending fosuserbundle
        empire_fos_userbundle:
            entity : { class: EmpireUsersBundle:UserEmpire, property : username}
        #This is a service for the provider class : Empire\UsersBundle\Security\Core\User\FOSUBUserProvider
        oauth_fos_userbundle:
            id: empire.oauth.user_provider
        in_memory:
            memory:
                users:
                     user:  { password: pass, roles: [ 'ROLE_USER' ] }
        # local registered users
        users_dataBase:
            entity : { class: EmpireUsersBundle:AbstractUser, property : username}

    firewalls:
        # disables authentication for assets and the profiler, adapt it according to your needs
        dev:
            pattern: ^/(_(profiler|wdt)|css|images|js)/
            security: false
        login:
            pattern:  ^/api/login
            stateless: true
            anonymous: true
            form_login:
                check_path:               /api/login_check
                provider: empire_fos_userbundle
                success_handler:          lexik_jwt_authentication.handler.authentication_success
                failure_handler:          lexik_jwt_authentication.handler.authentication_failure
                require_previous_session: false
                username_parameter: username
                password_parameter: password
            remember_me:
                secret: "%secret%"
                lifetime: 31536000  # 365 days in seconds
                path: /
                domain: ~
                # by default, the feature is enabled by checking a
                # checkbox in the login form (see below), uncomment the
                # following line to always enable it.
                #always_remember_me: true
        refresh:
            pattern:  ^/api/token/refresh
            stateless: true
            anonymous: true
        api:
            pattern:   ^/api
            stateless: true
            guard:
                provider: oauth_fos_userbundle
                authenticators:
                    - lexik_jwt_authentication.jwt_token_authenticator
        # This firewall is used to handle the public login area
        # This part is handled by the FOS User Bundle
        main:
            #anonymous: ~
            # activate different ways to authenticate
            # https://symfony.com/doc/current/security.html#a-configuring-how-your-users-will-authenticate
            #http_basic: ~
            # https://symfony.com/doc/current/security/form_login_setup.html
            #form_login: ~

            pattern : ^/
            context: user
            anonymous : true
            #provider: users_dataBase
            form_login:
                provider: fos_userbundle
                csrf_token_generator: security.csrf.token_manager
                check_path: /login_check
                login_path: /login
                failure_path:   null
                always_use_default_target_path: true
                default_target_path: /home
                #intention:                      authenticate
            oauth:
                resource_owners:
                    facebook:           "/login/check-facebook"
                    google:             "/login/check-google"
                    twitter:            "/login/check-twitter"
                login_path:        http://www.front-end.com/login
                failure_path:      http://www.front-end.com/login
                require_previous_session: false
                #always_use_default_target_path: true
                #default_target_path: /redirect_oauth
                #use_forward:       false
                #check_path: /connect_check
                provider: oauth_fos_userbundle #related to the service empire.oauth.user_provider . This parameter is required since Symfony4 for skiping conflicts
                oauth_user_provider:
                    #this is my custom user provider, created from FOSUBUserProvider - will manage the
                    #automatic user registration on your site, with data from the provider (facebook. google, etc.)
                    service: empire.oauth.user_provider
            logout :
                path: /logout
                target: /login 

    # Easy way to control access for large sections of your site
    # Note: Only the *first* access control that matches will be used
    access_control:
        - { path: ^/api/token/refresh, roles: IS_AUTHENTICATED_ANONYMOUSLY }
        - { path: ^/api/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
        - { path: ^/.*, role: IS_AUTHENTICATED_ANONYMOUSLY }

If you need any other information, please ask me that.

Thank you for any suggestion or any answer.

mohamedaymenkarmous commented 6 years ago

after trying to see in symfony profile's logs, I found that after HWIOauth authentication, when I load any other page using another controller, I get this Debug message:

Why I get disconnected ? The only thing that I want is using the facebook's user information in the symfony project as I said like generating a Lexik JWT Token to return it to the client.

mohamedaymenkarmous commented 6 years ago

[Marking this Issue as a Bug]

I found something intresting. It's true that I found this problem in symfony3.3 and symfony4.1. But I think that this problem can be solved in Symfony3.3 and not in Symfony4.1 because it seems like there is a bug.

Because after investigating in the way that I could perform some operations after the authentication, I found that in Symfony3.3, after authenticating to Facebook using HWIOauthBundle, when I visit another page, its that controller I can perform these operations without overriding "ConnectController::connectServiceAction" controller, I should only add the path of this page in "security.firewalls.oauth" path. So even after the first authentication try, I get redirected always to that page (in my example to oauth_redirect and generate the Lexik JWT Token there).

But in Symfony4.1 I don't know why after authenticating to Facebook using HWIOauthBundle, and visiting the other page in the same Symfony4.1 project, I successfully authenticate using HWIOauthBundle exactly in the page /login/check-facebook but when I visit the other controller, I found that this account have disconnected. In the logs it say "Token was deauthenticated after trying to refresh it.". But I didn't switch the user...

To provide more information, I'm sharing the logs of the HWIOauth Authentication and the logs of visiting another page (starting from no cookies in my browser):

The HWIOauth Authentication:

[2018-07-05 19:19:15] request.INFO: Matched route "hwi_oauth_service_redirect". {"route":"hwi_oauth_service_redirect","route_parameters":{"_route":"hwi_oauth_service_redirect","_controller":"HWI\\Bundle\\OAuthBundle\\Controller\\ConnectController::redirectToServiceAction","service":"facebook"},"request_uri":"https://www.example.com/login/facebook","method":"GET"} []
[2018-07-05 19:19:15] security.INFO: Populated the TokenStorage with an anonymous Token. [] []
[2018-07-05 19:19:16] request.INFO: Matched route "hwi_oauth_service_redirect". {"route":"hwi_oauth_service_redirect","route_parameters":{"_route":"hwi_oauth_service_redirect","_controller":"HWI\\Bundle\\OAuthBundle\\Controller\\ConnectController::redirectToServiceAction","service":"check-facebook"},"request_uri":"https://www.example.com/login/check-facebook?code=AQD-10ut_mfuQM7Foq5Eo........................","method":"GET"} []
[2018-07-05 19:19:16] doctrine.DEBUG: SELECT t1.username AS username_2, t1.username_canonical AS username_canonical_3, t1.email AS email_4, t1.email_canonical AS email_canonical_5, t1.enabled AS enabled_6, t1.salt AS salt_7, t1.password AS password_8, t1.last_login AS last_login_9, t1.confirmation_token AS confirmation_token_10, t1.password_requested_at AS password_requested_at_11, t1.roles AS roles_12, t1.id AS id_13, t1.last_name AS last_name_14, t1.first_name AS first_name_15, t1.status_group AS status_group_16, t0.last_name_facebook AS last_name_facebook_17, t0.first_name_facebook AS first_name_facebook_18, t0.username_facebook AS username_facebook_19, t0.email_facebook AS email_facebook_20, t0.profile_picture AS profile_picture_21, t0.facebook_id AS facebook_id_22, t0.facebook_access_token AS facebook_access_token_23, t0.time_creation AS time_creation_24, t0.time_edition AS time_edition_25, t0.time_last_activity AS time_last_activity_26, t0.status AS status_27, t1.user_type FROM user_facebook t0 INNER JOIN user_registered_abstract t1 ON t0.id = t1.id WHERE t0.facebook_id = ? LIMIT 1 ["xxxxxxxxxx"] []
[2018-07-05 19:19:16] doctrine.DEBUG: SELECT t1.username AS username_2, t1.username_canonical AS username_canonical_3, t1.email AS email_4, t1.email_canonical AS email_canonical_5, t1.enabled AS enabled_6, t1.salt AS salt_7, t1.password AS password_8, t1.last_login AS last_login_9, t1.confirmation_token AS confirmation_token_10, t1.password_requested_at AS password_requested_at_11, t1.roles AS roles_12, t1.id AS id_13, t1.last_name AS last_name_14, t1.first_name AS first_name_15, t1.status_group AS status_group_16, t0.last_name_facebook AS last_name_facebook_17, t0.first_name_facebook AS first_name_facebook_18, t0.username_facebook AS username_facebook_19, t0.email_facebook AS email_facebook_20, t0.profile_picture AS profile_picture_21, t0.facebook_id AS facebook_id_22, t0.facebook_access_token AS facebook_access_token_23, t0.time_creation AS time_creation_24, t0.time_edition AS time_edition_25, t0.time_last_activity AS time_last_activity_26, t0.status AS status_27, t1.user_type FROM user_facebook t0 INNER JOIN user_registered_abstract t1 ON t0.id = t1.id WHERE t0.facebook_id = ? LIMIT 1 ["xxxxxxxxxx"] []
[2018-07-05 19:19:16] php.INFO: User Deprecated: Calling Symfony\Component\Security\Core\User\UserChecker::checkPreAuth with an AdvancedUserInterface is deprecated since Symfony 4.1. Create a custom user checker if you wish to keep this functionality. {"exception":"[object] (ErrorException(code: 0): User Deprecated: Calling Symfony\\Component\\Security\\Core\\User\\UserChecker::checkPreAuth with an AdvancedUserInterface is deprecated since Symfony 4.1. Create a custom user checker if you wish to keep this functionality. at /var/www/my_symfony_project_path/vendor/symfony/security/Core/User/UserChecker.php:36)"} []
[2018-07-05 19:19:16] php.INFO: User Deprecated: Calling Symfony\Component\Security\Core\User\UserChecker::checkPostAuth with an AdvancedUserInterface is deprecated since Symfony 4.1. Create a custom user checker if you wish to keep this functionality. {"exception":"[object] (ErrorException(code: 0): User Deprecated: Calling Symfony\\Component\\Security\\Core\\User\\UserChecker::checkPostAuth with an AdvancedUserInterface is deprecated since Symfony 4.1. Create a custom user checker if you wish to keep this functionality. at /var/www/my_symfony_project_path/vendor/symfony/security/Core/User/UserChecker.php:68)"} []
[2018-07-05 19:19:16] security.INFO: User has been authenticated successfully. {"username":"a name"} []
[2018-07-05 19:19:16] doctrine.DEBUG: "START TRANSACTION" [] []
[2018-07-05 19:19:16] doctrine.DEBUG: UPDATE user_registered_abstract SET last_login = ? WHERE id = ? ["2018-07-05 19:19:16",1] []
[2018-07-05 19:19:16] doctrine.DEBUG: UPDATE user_facebook SET facebook_access_token = ? WHERE id = ? ["EAACtgqcdIRoBAMn2zImWvZAEZ [...]",1] []
[2018-07-05 19:19:16] doctrine.DEBUG: "COMMIT" [] []
[2018-07-05 19:19:16] security.DEBUG: Stored the security token in the session. {"key":"_security_user"} []
[2018-07-05 19:19:17] request.ERROR: Uncaught PHP Exception Symfony\Component\HttpKernel\Exception\NotFoundHttpException: "No route found for "GET /"" at /var/www/my_symfony_project_path/vendor/symfony/http-kernel/EventListener/RouterListener.php line 139 {"exception":"[object] (Symfony\\Component\\HttpKernel\\Exception\\NotFoundHttpException(code: 0): No route found for \"GET /\" at /var/www/my_symfony_project_path/vendor/symfony/http-kernel/EventListener/RouterListener.php:139, Symfony\\Component\\Routing\\Exception\\NoConfigurationException(code: 0):  at /var/www/my_symfony_project_path/var/cache/dev/srcDevDebugProjectContainerUrlMatcher.php:1309)"} []
[2018-07-05 19:19:18] request.INFO: Matched route "_wdt". {"route":"_wdt","route_parameters":{"_route":"_wdt","_controller":"web_profiler.controller.profiler::toolbarAction","token":"677966"},"request_uri":"https://www.example.com/_wdt/677966","method":"GET"} []

I'm authenticated now. (I have an error in the end with the route / because I didn't declare it yet but it's ok)

Visiting the other page (/oauth_redirect) :

[2018-07-05 19:19:24] request.INFO: Matched route "oauth_redirect". {"route":"oauth_redirect","route_parameters":{"_route":"oauth_redirect","_controller":"Empire\\UsersBundle\\Controller\\ConnectController::oauthRedirectAction"},"request_uri":"https://www.example.com/oauth_redirect","method":"GET"} []
[2018-07-05 19:19:24] security.DEBUG: Read existing security token from the session. {"key":"_security_user","token_class":"HWI\\Bundle\\OAuthBundle\\Security\\Core\\Authentication\\Token\\OAuthToken"} []
[2018-07-05 19:19:24] doctrine.DEBUG: SELECT t1.username AS username_2, t1.username_canonical AS username_canonical_3, t1.email AS email_4, t1.email_canonical AS email_canonical_5, t1.enabled AS enabled_6, t1.salt AS salt_7, t1.password AS password_8, t1.last_login AS last_login_9, t1.confirmation_token AS confirmation_token_10, t1.password_requested_at AS password_requested_at_11, t1.roles AS roles_12, t1.id AS id_13, t1.last_name AS last_name_14, t1.first_name AS first_name_15, t1.status_group AS status_group_16, t0.profile_picture AS profile_picture_17, t0.time_creation AS time_creation_18, t0.time_edition AS time_edition_19, t0.time_last_activity AS time_last_activity_20, t0.status AS status_21, t1.user_type FROM user_empire t0 INNER JOIN user_registered_abstract t1 ON t0.id = t1.id WHERE t1.id = ? LIMIT 1 [1] []
[2018-07-05 19:19:24] doctrine.DEBUG: SELECT t1.username AS username_2, t1.username_canonical AS username_canonical_3, t1.email AS email_4, t1.email_canonical AS email_canonical_5, t1.enabled AS enabled_6, t1.salt AS salt_7, t1.password AS password_8, t1.last_login AS last_login_9, t1.confirmation_token AS confirmation_token_10, t1.password_requested_at AS password_requested_at_11, t1.roles AS roles_12, t1.id AS id_13, t1.last_name AS last_name_14, t1.first_name AS first_name_15, t1.status_group AS status_group_16, t0.last_name_facebook AS last_name_facebook_17, t0.first_name_facebook AS first_name_facebook_18, t0.username_facebook AS username_facebook_19, t0.email_facebook AS email_facebook_20, t0.profile_picture AS profile_picture_21, t0.facebook_id AS facebook_id_22, t0.facebook_access_token AS facebook_access_token_23, t0.time_creation AS time_creation_24, t0.time_edition AS time_edition_25, t0.time_last_activity AS time_last_activity_26, t0.status AS status_27, t1.user_type FROM user_facebook t0 INNER JOIN user_registered_abstract t1 ON t0.id = t1.id WHERE t1.id = ? LIMIT 1 [1] []
[2018-07-05 19:19:24] security.DEBUG: Token was deauthenticated after trying to refresh it. {"username":"a name","provider":"FOS\\UserBundle\\Security\\EmailUserProvider"} []
[2018-07-05 19:19:24] security.INFO: Populated the TokenStorage with an anonymous Token. [] []

And I get disconnected.

Even when I try accessing to this same page again, I don't find the user which have been disconnected:

[2018-07-05 19:19:34] request.INFO: Matched route "oauth_redirect". {"route":"oauth_redirect","route_parameters":{"_route":"oauth_redirect","_controller":"Empire\\UsersBundle\\Controller\\ConnectController::oauthRedirectAction"},"request_uri":"https://www.example.com/oauth_redirect","method":"GET"} []
[2018-07-05 19:19:34] security.INFO: Populated the TokenStorage with an anonymous Token. [] []
[2018-07-05 20:14:08] request.INFO: Matched route "oauth_redirect". {"route":"oauth_redirect","route_parameters":{"_route":"oauth_redirect","_controller":"Empire\\UsersBundle\\Controller\\ConnectController::oauthRedirectAction"},"request_uri":"https://www.example.com/oauth_redirect","method":"GET"} []
[2018-07-05 20:14:08] security.INFO: Populated the TokenStorage with an anonymous Token. [] []

I tested this use case in Symfony3.3 and I don't get this problem. The user was authenticated and can't be disconnected.

If you need any other information, please ask me that.

Thank you for any suggestion or any answer.

mohamedaymenkarmous commented 6 years ago

I found the problem of this reported bug and it was not a bug.

It seems like this Bundle didn't well work because of this silent error reported in the logs file as php.INFO:

[2018-07-05 19:19:16] php.INFO: User Deprecated: Calling Symfony\Component\Security\Core\User\UserChecker::checkPreAuth with an AdvancedUserInterface is deprecated since Symfony 4.1. Create a custom user checker if you wish to keep this functionality. {"exception":"[object] (ErrorException(code: 0): User Deprecated: Calling Symfony\\Component\\Security\\Core\\User\\UserChecker::checkPreAuth with an AdvancedUserInterface is deprecated since Symfony 4.1. Create a custom user checker if you wish to keep this functionality. at /var/www/my_symfony_project_path/vendor/symfony/security/Core/User/UserChecker.php:36)"} []
[2018-07-05 19:19:16] php.INFO: User Deprecated: Calling Symfony\Component\Security\Core\User\UserChecker::checkPostAuth with an AdvancedUserInterface is deprecated since Symfony 4.1. Create a custom user checker if you wish to keep this functionality. {"exception":"[object] (ErrorException(code: 0): User Deprecated: Calling Symfony\\Component\\Security\\Core\\User\\UserChecker::checkPostAuth with an AdvancedUserInterface is deprecated since Symfony 4.1. Create a custom user checker if you wish to keep this functionality. at /var/www/my_symfony_project_path/vendor/symfony/security/Core/User/UserChecker.php:68)"} []

So after removing the call of the AdvancedUserInterface class and its implements by the User class, the problem of the anormaly disconnecting the authenticated user disappeared.

I give everyone the advice to fix all the "Deprecated" problems even if they did not stop the bundle from working and even if this is a silent error. (for more information, please check the Deprecations tab in the Logs menu of the /_profiler/ pages enabled in the dev mode).

I hope this could help any other person having this problem.

Thank you