1

I'm working on adding Oauth login to a Symfony2 site. I have the bundle working and configured with paypal and Facebook. I have remember me working. My issue is when a user comes back via remember me and tries to reauthenticate via oauth it tells me the accounts were connected fully but doesn't authenticate me at all. Re logging in with a user name and password works fine.

Config.yml

hwi_oauth:
    # name of the firewall in which this bundle is active, this setting MUST be set
    firewall_name: main
    connect:
        account_connector: app.provider.oauth
        confirmation: true
    resource_owners:
        facebook:
            type:                facebook
            client_id:           %facebook_client_id%
            client_secret:       %facebook_client_secret%
            scope:               "email, public_profile"
            infos_url:           "https://graph.facebook.com/me?fields=id,name,first_name,last_name,email,picture.type(large)"
            paths:
                email: email
            options:  
                csrf: true           
        paypal:
            type:                paypal
            client_id:           %paypal_client_id%
            client_secret:       %paypal_client_secret%
            scope: 'openid profile email'
            options: 
                csrf: true

Security.yml

firewalls:
        main:
            pattern: ^/
            anonymous: ~
            oauth:
                failure_path: /login
                login_path:  /login
                check_path: /login
                provider: fos_userbundle
                remember_me:  true
                always_use_default_target_path: false
                default_target_path: /login
                resource_owners:
                    facebook: "/external-login/check-facebook"
                    paypal: "/external-login/check-paypal"
                    amazon: "/external-login/check-amazon"
                oauth_user_provider:
                    service: app.provider.oauth
                remember_me:
                    key:      %secret% 
                    lifetime: 31536000  
                    path:     /
                    domain:   ~
                    always_remember_me: true
            form_login:
                login_path:  /login
                check_path:  /login_check
                success_handler: authentication_handler
                failure_handler: authentication_handler
                csrf_provider: form.csrf_provider
                remember_me:  true
            logout:       true
            anonymous:    true
            switch_user: { role: ROLE_ALLOWED_TO_SWITCH, parameter: _new_user }
            remember_me:
                key:      %secret% 
                lifetime: 31536000 
                path:     /
                domain:   ~
                always_remember_me: true

Routing.yml

hwi_oauth_redirect:
    resource: "@HWIOAuthBundle/Resources/config/routing/redirect.xml"
    prefix:   /connect

hwi_oauth_login:
    resource: "@HWIOAuthBundle/Resources/config/routing/login.xml"
    prefix:   /external-login/

hwi_oauth_connect:
    resource: "@HWIOAuthBundle/Resources/config/routing/connect.xml"
    prefix:  /external-login/

facebook_login:
    pattern: /external-login/check-facebook

paypal_login:
    pattern: /external-login/check-paypal

Thanks!

Jeremy
  • 494
  • 1
  • 8
  • 20
  • Hi @Jeremy, any update on this question? Did you solve it?I think I got the same problem https://stackoverflow.com/questions/53212879/hwioauthbundle-oauthtoken-userresponse-of-abstractresourceowner-contains-errors – Nadjib Nov 08 '18 at 17:19
  • It was sloppy but I modified the vendor library as seen here: https://github.com/rynner/HWIOAuthBundle/commit/3951f23db5bd17e1a48a91d3b9c637714811dad7 I don't think they approved this and added support for it on their own. – Jeremy Nov 08 '18 at 22:20
  • So you mean you added the modified parts inside the vendor code? Is it a good practice? Does it work well? I mean, does it allow you to use the offline mode of google OAUth?? Thank you in advance, – Nadjib Nov 08 '18 at 23:05
  • You really shouldn't modify parts of vendor code unless your 100% sure it will get accepted into the main repo or you will never update the library, ever. It worked fine for us until we rebuilt/switched platforms for our site. I have no idea what offline mode of google OAUTH is. – Jeremy Nov 09 '18 at 18:29

1 Answers1

0

My Own Answer (Solved)

so, I finally found the solution to my problem, maybe it will help others to solve it.

This is why the token failed to be authenticated.

For a returning user, on my home page, I called the is_granted('ROLE_ADMIN'), so the token was authenticated by the method authenticate of the AuthenticationManager, which was called in the AuthorizationChecker, as you can see below

final public function isGranted($attributes, $object = null)
    {
        if (null === ($token = $this->tokenStorage->getToken())) {
            throw new AuthenticationCredentialsNotFoundException('The token storage contains no authentication token. One possible reason may be that there is no firewall configured for this URL.');
        }

        if ($this->alwaysAuthenticate || !$token->isAuthenticated()) {
            $this->tokenStorage->setToken($token = $this->authenticationManager->authenticate($token));
        }

        if (!\is_array($attributes)) {
            $attributes = array($attributes);
        }

        return $this->accessDecisionManager->decide($token, $attributes, $object);
    }

The AuthenticationManager then uses the method authenticate of the OAuthProvider, which is one of its dependencies.This method looks like this:

 * {@inheritdoc}
 */
public function authenticate(TokenInterface $token)
{
    if (!$this->supports($token)) {
        return;
    }

    // fix connect to external social very time
    if ($token->isAuthenticated()) {
        return $token;
    }

    /* @var OAuthToken $token */
    $resourceOwner = $this->resourceOwnerMap->getResourceOwnerByName($token->getResourceOwnerName());

    $oldToken = $token->isExpired() ? $this->refreshToken($token, $resourceOwner) : $token;
    $userResponse = $resourceOwner->getUserInformation($oldToken->getRawToken());

    try {
        $user = $this->userProvider->loadUserByOAuthUserResponse($userResponse);
    } catch (OAuthAwareExceptionInterface $e) {
        $e->setToken($oldToken);
        $e->setResourceOwnerName($oldToken->getResourceOwnerName());

        throw $e;
    }

    if (!$user instanceof UserInterface) {
        throw new AuthenticationServiceException('loadUserByOAuthUserResponse() must return a UserInterface.');
    }

    $this->userChecker->checkPreAuth($user);
    $this->userChecker->checkPostAuth($user);

    $token = new OAuthToken($oldToken->getRawToken(), $user->getRoles());
    $token->setResourceOwnerName($resourceOwner->getName());
    $token->setUser($user);
    $token->setAuthenticated(true);
    $token->setRefreshToken($oldToken->getRefreshToken());
    $token->setCreatedAt($oldToken->getCreatedAt());

    return $token;
}

As you can see, for a returning user, the OAuthToken is expired, so the OAuthProvider will try to refresh it

$oldToken = $token->isExpired() ? $this->refreshToken($token, $resourceOwner) : $token;

My issue was that my previous OAuthToken had a null refresh_token option. Although I had set the access_type to offline on my config.yml for the HWIOAuthBundle, it would not work, the refresh_token would still be null

hwi_oauth
    resource_owners:
        google:
            type: google
            client_id: "%google_app_id%"
            client_secret: "%google_app_secret%"
            scope: "https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/plus.login https://www.googleapis.com/auth/plus.me https://www.googleapis.com/auth/user.birthday.read"
            options:
                access_type: offline
                prompt: consent

So why was my refresh_token always null? I fell on this other question on stack over flow Not receiving Google OAuth refresh token

The answers tell you exactly why google does not send you a refresh_token. Once you used the OAuth authentication without the refresh_token, google, will never send you a refresh token, unless you prompt the APi for consent.

This is why I added the prompt: consent to my configuration file.

So why did all of this generate a bug?

If I don't get the refresh_token from the google API, the userResponse of the OAuthProvider, which is based on the google API response, would have a null username attribute.

$userResponse = $resourceOwner->getUserInformation($oldToken->getRawToken());

Therefore, when the OAuthProvider tries to load a user thanks to the OAuthUserProvider, the username would be null so the OAuthUserProvider would not find the user.

$user = $this->userProvider->loadUserByOAuthUserResponse($userResponse);

Therefore, I needed to get the refresh_token back from google in order to be able to have a correct userResponse with a non-empty username attribute.

Changing my configuration for hwi_oauth and adding these two options to google ressource_owners solved my problem

options:
    access_type: offline
    prompt: consent
Nadjib
  • 311
  • 6
  • 17