SocalNick / ScnSocialAuth

Uses the HybridAuth PHP library to Enable authentication via Google, Facebook, Twitter, Yahoo!, etc for the ZfcUser ZF2 module.
BSD 3-Clause "New" or "Revised" License
216 stars 110 forks source link

Persist Access to API #195

Closed gsomoza closed 9 years ago

gsomoza commented 9 years ago

I'm building a small tool that lets you log-in (and register) with Github and Bitbucket and then consumes the Github and BitBucket APIs using that same login session. So far I'm able to register and authenticate users with both services, as well as consume the APIs while the user is logged-in to my tool.

But I'm having trouble persisting the connections to the APIs. For example, given a user that logged in with both Github and BitBucket: if he returns to the site and logs in with Github only, then I'd like to still be able to consume the BitBucket API (e.g. through a stored access token or something like that, I imagine). Right now I have to re-login with each of the services independently in order to allow the tool to query each of their APIs.

I've been able to store the BitBucket access token into the user_provider table by overriding the UserProviderMapper's linkUserToProvider, but that apparently only gets called on the "add provider" execution flow and NOT while authenticating.

Is there any better way I can accomplish this for both the authentication workflow and the "add/link" workflow?

gsomoza commented 9 years ago

By the way in my case this is related to the doctrine ORM integration in this other repository as well: https://github.com/SocalNick/ScnSocialAuthDoctrineORM

SocalNick commented 9 years ago

@gabrielsomoza - tbh, i'm not so familiar with using this. I made the module when I was doing more ZF2 work, but I currently don't do any PHP / ZF2 at work.

Having said that, I might be able to guide you in the right direction. It's probably a really bad idea to store access tokens in your database, you should really only use an access token provided by the user's browser that they have obtained from the OAuth provider. When the access token expires, you can usually exchange a refresh token for a new access token. I recommend reading up on OAuth2, it's a heavy read, but this graph really spells it out.

So once you are familiar with OAuth2 in general, you'll have to become more familiar with how ScnSocialAuth provides access to the HybridAuth API. I recently responded to #196 with instructions on how to get access to the HybridAuth API, but I'll paste the code below:

$hybridAuth = $this->getServiceLocator()->get('HybridAuth');
// if the user is connected, authenticate will return an instance of the provider adapter
$adapter = $hybridAuth->authenticate('facebook');
if (!$adapter->isUserConnected()) {
    // do something sensible for a logged out user...
    return $this->redirect()->toRoute('some-logged-out-route');
}
$userProfile = $adapter->getUserProfile();

You'll likely be able to do the exact same thing, or something reasonably close as long as a user has already given your application access to any of the OAuth providers. Making calls to HybridAuth may result in the user being redirected to the provider, but if they have an access token or refresh token, they will be immediately redirected back to your site since they've already granted you access.

Hope this helps, it's just a matter of familiarizing yourself with OAuth2 and the various APIs.

Enjoy!

-Nick

gsomoza commented 9 years ago

Thanks for taking the time to respond. Let me see if I understood this correctly:

  1. The user creates an account in my system by using Github.
  2. Then he also links his BitBucket account.
  3. He uses the system and then logs out. I don't store any access tokens anywhere. I only store two records which indicate that this particular user has once been authenticated through Github and BitBucket.
  4. After a certain amount of time, the user returns to the system and chooses to login with BitBucket. After the normal BitBucket authentication process, I pull the providers for that user and see he also authenticated in the past with Github. So at this point I should call $hybridAuth->authenticate('github') and that will be enough to retrieve the user's Github profile through the adapter? And all of this will happen within one single end-user request to my server (e.g. no redirects will be necessary for the end-user)?

If that's possible then that would indeed seem like the most flexible solution because we should of course avoid storing sensitive information if not needed. However, I wasn't able to achieve that: authenticating with multiple services with a single end-user interaction (e.g. have access to all previously-linked social networks by simply logging in through just ONE of them).

As for the alternatives: storing the access token in the end-user's browser with cookies wouldn't work for my use-case: I'd like people to use multiple devices to consume my service and therefore storing those tokens in each device is not practical.

The way I solved it so far is to strongly encrypt the access tokens and store them alongside the corresponding user_provider record - i.e. treat those tokens as passwords. By creating this ticket I meant to see if anyone else (or maybe yourself) would be interesting on making this a more generic mechanism offered by this plugin. That is, of course, assuming that storing encrypted access tokens is indeed an acceptable solution.

SocalNick commented 9 years ago

Re-opening in case others would like to comment.

SocalNick commented 9 years ago

@gabrielsomoza can you think of another site that allows this behavior? I would like to see the use case in action if possible.

gsomoza commented 9 years ago

I imagine a site like Hootsuite would do something like this. Basically any site that allows people to connect to more than one social network and then would need to consume that social network's API whenever the user logs in (without going through the authorization process again).

SocalNick commented 9 years ago

I guess a site like Klout must also keep access tokens in order to monitor your social sites.

Shot in the dark, but maybe you could override the underlying Hybrid_Auth::$store after my module creates the Hybrid_Auth object. It would just have to implement the Hybrid_Storage_Interface. If your storage implementation used a database, maybe you could use that data while the user isn't actively engaging with your site. Like I said, shot in the dark so you'll have to do some debugging to see if Hybid_Auth stores data you can use to achieve this.

Maybe these links will help: http://hybridauth.sourceforge.net/userguide/HybridAuth_Sessions.html https://developers.google.com/identity/protocols/OAuth2WebServer#offline

gsomoza commented 9 years ago

Great, thanks for the guidance / insights! I'll investigate that approach.