GithubHelp home page GithubHelp logo

Comments (16)

stloyd avatar stloyd commented on May 17, 2024

Access token can be accessed from response (using PathUserResponse#getAccessToken() mehod), in this way, you can save/re-validate token when user signs-in in your UserProvider.

There is no integrated way to handle invalidation/expiration of tokens as it was not required, but this bundle allows easily extending of specific resource owners allowing you to handle that if you need it.

from hwioauthbundle.

lmammino avatar lmammino commented on May 17, 2024

In wich part of the login/registration process can i access to the PathUserResponse?
Can you provide a quick demo snippet?

from hwioauthbundle.

stloyd avatar stloyd commented on May 17, 2024

Sorry, mentioned wrong class, mainly because I forgot that the access token is also set in built-in OAuthToken.

I guess you know (at least in theory) how to connect users and store specific info in db ;-)

from hwioauthbundle.

lmammino avatar lmammino commented on May 17, 2024

Don't worry. How can I retrieve the OAuthToken instance and where?
I just tried something, but mostly experimenting. As you know the documentation for the bundle is incomplete and many topics are not covered in detail. Some more examples would be great!
Can you provide an example on how to store the access token at registration/connect and update it once the user logins again with facebook?

Thanks in advance

from hwioauthbundle.

stloyd avatar stloyd commented on May 17, 2024

About how to get access token in any part of Symfony:

$accessToken = $this->container->get('security.context')->getToken()->getAccessToken();

To save this token at user entity at login phase, you could implement own UserProvider with interface OAuthAwareUserProviderInterface, as example look at built-in EntityUserProvider.

from hwioauthbundle.

lmammino avatar lmammino commented on May 17, 2024

Thanks @stloyd .
Thanks to your comment I think I understood something more.
I partly solved my issues and I'm currently able to store the user facebook access token when he/she signs up or connects with facebook (but I am not able to update the token, in case it changed, when the user logins again using facebook).
I'll point out what I did. I settled up a test project here that you can check out to find out some detail that i would probably miss.

  1. I added the field facebookAccessToken in my User class (based on FOSUserBundle)

  2. I created my custom user provider by extending the default FOSUBUserProvider class

  3. I tweaked the connect() method by adding some lines to store the facebook access token on the facebookAccessToken field. (Follows the whole implementation). I tried to make the integration somewhat generic to be able to store access token for other services just by adding more fields to the user entity (e.g. githubAccessToken, twitterAccessToken, etc.).

    <?php
    
    namespace LMammino\Bundle\JHACBundle\Security\Core\User;
    
    use HWI\Bundle\OAuthBundle\OAuth\Response\UserResponseInterface;
    use HWI\Bundle\OAuthBundle\Security\Core\User\FOSUBUserProvider as BaseClass;
    
    class FOSUBUserProvider extends BaseClass
    {
    
        /**
         * {@inheritDoc}
         */
        public function connect($user, UserResponseInterface $response)
        {
            $property = $this->getProperty($response);
            $setter = 'set'.ucfirst($property);
    
            if (!method_exists($user, $setter)) {
                throw new \RuntimeException(sprintf("Class '%s' should have a method '%s'.", get_class($user), $setter));
            }
    
            $username = $response->getUsername();
    
            if (null !== $previousUser = $this->userManager->findUserBy(array($property => $username))) {
                $previousUser->$setter(null);
                $this->userManager->updateUser($previousUser);
            }
    
            $user->$setter($username);
    
            $serviceAccessTokenName = $response->getResourceOwner()->getName() . 'AccessToken';
            $serviceAccessTokenSetter = 'set' . ucfirst($serviceAccessTokenName);
    
            if(method_exists($user, $serviceAccessTokenSetter))
                $user->$serviceAccessTokenSetter($response->getAccessToken());
    
            $this->userManager->updateUser($user);
        }
    
    }
    
    Just few questions now:
    
    1. Is this a good/correct approach (i.e. extendible, scalable, compliant, etc...)?
    2. What to do to check the access token each time the user logins with facebook? It would be great to be able to refresh the access token stored in the database if it changed (for any reason: e.g. invalidated, expired or app secret changed)
    3. What to do if i want my users to be able to remove their _connection_? I thought that just removing the stored facebook id and access token would be enough, but it would be glad to have some confirmation...
    
    I also would like to suggest to improve the configurability of the default FOSUBUserProvider just to handle this case (storing the access token of each service). I thought that would not be so difficult to implement this kind of configuration:
hwi_oauth:
    fosub:
        properties:
            github: githubId
            google: googleId
            facebook: facebookId
        access_token:
            github: githubAccessCode
            google: googleAccessCode
            facebook: facebookAccessToken

or

hwi_oauth:
   fosub:
       properties:
            github: 
                id: githubId
                accessToken: githubAccessToken
            google: 
                id: googleId
                accessToken: googleAccessToken
            facebook: 
                id: facebookId
                accessToken: facebookAccessToken

(but the latter would break compatibility)

Thanks again for the great support!

from hwioauthbundle.

stloyd avatar stloyd commented on May 17, 2024

Hey @lmammino,

For case you mentioned: update token in case of users sign-in, as I mentioned you need to update other method, called loadUserByOAuthUserResponse, as you extend the FOSUBUserProvider, it should be something like:

/**
 * {@inheritdoc}
 */
public function loadUserByOAuthUserResponse(UserResponseInterface $response)
{
    $user = parent::loadUserByOAuthUserResponse($response);

    $serviceName = $response->getResourceOwner()->getName();
    $setter = 'set' . ucfirst($serviceName) . 'AccessToken';

    if (method_exists($user, $setter)) {
        $user->$setter($response->getAccessToken());
    }

    return $user;
}

Note: this will update token on every sign-in call.

About questions:

  1. IMO yes, of course if you will not add to much calls to update etc., some look-up in your code, how often such updates are called would be necessary,
  2. Update of token just suggested above, invalidation etc., with built-in code it's not simple IMO, but you can easily extend resource provider to add support for it, in overall case for this bundle, built-in support is not necessary, as it always tries to get up-to-date token from resource,
  3. You mean: remove connection to resource owner at bundle controller state ? It's not supported, mainly because not all resource owners provide API way to do this, but if your user removes access at state of resource owner (i.e. removes access for your app at facebook), you will be not notified about that, so your access token will just get an error with access denied, but if user removes that access and tries again to sign-in using resource owner, he will be asked to give your app access, same way as he would do this first time.

About new fields: this bundle is not right place for this, mainly because it's not necessary, and in bundle itself it will be never used, also you can very easily add this to your bundle (same way you have done it already).

from hwioauthbundle.

lmammino avatar lmammino commented on May 17, 2024

Thanks again @stloyd ! ;)
Your explanation was very complete and clear and I finally got a good idea about how the bundle works.

Just one more question about updating the access token when user logins:

Should I call the method $this->userManager->updateUser($user) to effectively trigger the update at database level, or its done out-of-the-box?

Anyway I suppose i can create a getter for the service access token (e.g. getFacebookAccessToken) and use it to compare the two access tokens. I would trigger the update just if the two tokens are different. Doing so i would avoid updating the user instance if not necessary.

So definitely i suppose i would have a code like this:

/**
 * {@inheritdoc}
 */
public function loadUserByOAuthUserResponse(UserResponseInterface $response)
{
    $user = parent::loadUserByOAuthUserResponse($response);

    $serviceName = $response->getResourceOwner()->getName();
    $serviceAccessTokenSetter = 'set' . ucfirst($serviceName) . 'AccessToken';
    $serviceAccessTokenGetter = 'get' . ucfirst($serviceName) . 'AccessToken';

    if ( method_exists($user, $serviceAccessTokenSetter) &&
         method_exists($user, $serviceAccessTokenGetter) &&
         $user->$serviceAccessTokenGetter() !== $response->getAccessToken()
       )
    {
        $user->$serviceAccessTokenSetter($response->getAccessToken());
        $this->userManager->updateUser($user);
    }

    return $user;
}

from hwioauthbundle.

stloyd avatar stloyd commented on May 17, 2024

In overall if it's your code, I would suggest to remove all those method_exists() calls to save a bit, mainly because you can be sure you have implemented those method in your entity, those checks are useful only in case that your bundle is open sourced, and user can implement entity on his own.

And yes, I forgot about update of user in db, personally I don't like changing user object in loaders, as it's smelly for me, I prefer using events here, but in most cases such approach is good enough.

About comparing tokens: it's good idea, but in some edge cases could not work as expected, mainly because as mentioned before, this bundle requests for up-to-date tokens, as there is no easy way to make (in)validation (of tokens) in easy way for all built-in providers.

from hwioauthbundle.

lmammino avatar lmammino commented on May 17, 2024

Thanks for the good suggestions once more @stloyd. I'll surely apply them in production.

Anyway, I didn't get the last point.
You said that the bundle requests up-to-date tokens. So why comparing up-to-date token with my previously stored token may not work?
Can you provide me a direct example of one of the edge case you mentioned?

I would also be glad to have an example about how to update the user by using events. (Probably it would be a good lesson to learn something more about Symphony)

from hwioauthbundle.

stloyd avatar stloyd commented on May 17, 2024

About edge case, it's more like theory now, not sure it can be easily found. Let's say you call facebook, you get an token and it will expire within one day (86400 secs), but you have in you app session lifetime set to one hour (3600 secs), so you will logout user automatically after one hour, and force him to sign-in, and here you could get new access token from facebook (not sure how they handle such requests), so at least in theory, you will have two valid tokens =) as said, it's really edge case, and you probably will not meet it... =)

About events: Symfony (not Symphony :P), has built-in security events, which are called when provider success or fails to authenticate user.

To use them you need to set-up your own event listener, which in you case, should look for security.authentication.success event to be called.

In event listener, you need to inject User Manager (from FOSUB, or your own if you have such) or Entity/Object Manager (from Doctrine), as an event object you will get AuthenticationEvent, from which you can easily extract OAuthToken and wired with it User entity, it could look like:

services:
    success_login_listener:
        class: 'LMammino\Bundle\JHACBundle\Listener\SecuritySuccessListener'
        arguments: [@fos_user.user_manager]
        tags:
            - { name: kernel.event_listener, event: security.authentication.success, method: handle }
// src/LMammino/Bundle/JHACBundle/Listener/SecuritySuccessListener.php
namespace LMammino\Bundle\JHACBundle\Listener;

use FOS\UserBundle\Model\UserManagerInterface;
use FOS\UserBundle\Model\UserInterface;
use Symfony\Component\Security\Core\Event\AuthenticationEvent;

class SecuritySuccessListener
{
    private $userManager;

    public function __construct(UserManagerInterface $userManager)
    {
        $this->userManager = $userManager;
    }

    public function handle(AuthenticationEvent $event)
    {
        $user = $event->getAuthenticationToken()->getUser();

        if ($user instanceof UserInterface) {
            // do an update of user object with tokens here
            $this->userManager->updateUser($user);
        }
    }
}

from hwioauthbundle.

stloyd avatar stloyd commented on May 17, 2024

Note that this listener will also be called on normal form sign-in, so you probably should check in handle() method for that the token is an instance of OAuthToken from this bundle if you plan to use such approach for signing-in too.

from hwioauthbundle.

lmammino avatar lmammino commented on May 17, 2024

Whoops! I know it's Symfony and not Symphony, but it's not natural to me and sometimes my hands go by themselves on my keyboard! =)

As far as I know, facebook does not allows multiple active accesstoken anymore. If a new token is generated ( e.g. due to app permission change, user changing password or app changing secret) the old one(s) is(are) invalidated.
So, in the scenario you proposed, I suppose that the stored access token would not be valid while the user remains logged in thanks to the app session lifetime, but when he/she disconnects and logs in again the access token should be updated and becomes functional again.
I know it's not perfect but it's a good compromise at the moment.
I think a better solution would be to handle the token invalidation/expiration on the app internal social API layer (that may run as parallel process or in a separate machine). When the app triggers a request to facebook it should check if the given token has expired or it's not valid anymore (by reading the facebook api call response code) and, eventually, notify the user who owns the token (e.g. by mail) to disconnect and reconnect again to the website to trigger a token update.

Thanks for the "quick lesson" about Symfony security events... I'll surely adopt your code!

from hwioauthbundle.

asm89 avatar asm89 commented on May 17, 2024

@lmammino Can this issue be closed? :)

from hwioauthbundle.

lmammino avatar lmammino commented on May 17, 2024

of course @asm89 👍

from hwioauthbundle.

asm89 avatar asm89 commented on May 17, 2024

Check :)

from hwioauthbundle.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.