Skip to content

Instantly share code, notes, and snippets.

@bekco
Last active May 8, 2019 06:36
Show Gist options
  • Star 10 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save bekco/c14252d616b3bc17d5d2dd64fd543669 to your computer and use it in GitHub Desktop.
Save bekco/c14252d616b3bc17d5d2dd64fd543669 to your computer and use it in GitHub Desktop.
How to Authenticate (Facebook, Google +, Twitter etc.) users with HWIOauthBundle and FOSOauthServerBundle from iOS, Android or third party application using API.
<?php
/**
* User: bekco (Behçet Mutlu)
* Date: 17/11/16
* Time: 17:52
*/
namespace Acme\Oauth2Bundle\Oauth\Extension;
use Facebook\Exceptions\FacebookAuthenticationException;
use Facebook\Exceptions\FacebookAuthorizationException;
use FOS\OAuthServerBundle\Storage\GrantExtensionInterface;
use HWI\Bundle\OAuthBundle\OAuth\ResourceOwnerInterface;
use HWI\Bundle\OAuthBundle\OAuth\Response\UserResponseInterface;
use HWI\Bundle\OAuthBundle\Security\Core\Authentication\Token\OAuthToken;
use HWI\Bundle\OAuthBundle\Security\Core\Exception\OAuthAwareExceptionInterface;
use HWI\Bundle\OAuthBundle\Security\Core\User\OAuthAwareUserProviderInterface;
use OAuth2\Model\IOAuth2Client;
use Symfony\Component\Security\Core\Exception\AuthenticationServiceException;
use Symfony\Component\Security\Core\User\UserInterface;
class ResourceOwnerGrantExtension implements GrantExtensionInterface
{
protected $userProvider = null;
protected $resourceOwner = null;
public function __construct(OAuthAwareUserProviderInterface $userProvider, ResourceOwnerInterface $resourceOwner)
{
$this->userProvider = $userProvider;
$this->resourceOwner = $resourceOwner;
}
/**
* Check any extended grant types.
*
* @param IOAuth2Client $client
* @param array $inputData Unfiltered input data. The source is *not* guaranteed to be POST (but is likely to be).
* @param array $authHeaders Authorization headers
* @return array|bool Returns false if the authorization is rejected or not support. Returns true or an associative array if you
* want to verify the scope:
* @throws \Exception
* @code
* return array(
* 'scope' => <stored scope values (space-separated string)>,
* );
* @endcode
*
* @see \OAuth2\IOAuth2GrantExtension::checkGrantExtension
*/
public function checkGrantExtension(IOAuth2Client $client, array $inputData, array $authHeaders)
{
if (!isset($inputData['access_token'])) {
return false;
}
$token = new OAuthToken($inputData);
try {
// Try to get the user with the token from Open Graph
/**
* @var $userResponse UserResponseInterface
*/
$userResponse = $this->resourceOwner->getUserInformation([
'access_token' => $token->getAccessToken()
]);
try {
// Check if a user match in database with the resource owner id
$user = $this->userProvider->loadUserByOAuthUserResponse($userResponse);
} catch (OAuthAwareExceptionInterface $e) {
$e->setToken($token);
$e->setResourceOwnerName($token->getResourceOwnerName());
throw $e;
}
if (!$user instanceof UserInterface) {
throw new AuthenticationServiceException('loadUserByOAuthUserResponse() must return a UserInterface.');
}
// Else, return the access_token for the user
else {
return array(
'data' => $user
);
}
}
catch(FacebookAuthorizationException $e) {
return false;
} catch(FacebookAuthenticationException $e) {
return false;
}
}
}
services:
oauth.facebook_extension:
class: Acme\Oauth2Bundle\Oauth\Extension\ResourceOwnerGrantExtension
arguments:
userProvider: "@acme_fosub_user_provider"
respourceOwner: "@hwi_oauth.resource_owner.facebook"
tags:
- { name: fos_oauth_server.grant_extension, uri: 'https://acme.com/facebook' }
oauth.google_extension:
class: Acme\Oauth2Bundle\Oauth\Extension\ResourceOwnerGrantExtension
arguments:
userProvider: "@acme_fosub_user_provider"
respourceOwner: "@hwi_oauth.resource_owner.google"
tags:
- { name: fos_oauth_server.grant_extension, uri: 'https://acme.com/google' }
oauth.twitter_extension:
class: Acme\Oauth2Bundle\Oauth\Extension\ResourceOwnerGrantExtension
arguments:
userProvider: "@acme_fosub_user_provider"
respourceOwner: "@hwi_oauth.resource_owner.twitter"
tags:
- { name: fos_oauth_server.grant_extension, uri: 'https://acme.com/twitter' }
@bekco
Copy link
Author

bekco commented Nov 18, 2016

Usage:

$ curl -XGET "https://acme.com/oauth/v2/token?
grant_type=https://acme.com/facebook&
client_id=1_1&
client_secret=secret&
access_token=facebook_access_token

@dilipraya
Copy link

use Facebook\Exceptions\FacebookAuthenticationException; not found

@bekco
Copy link
Author

bekco commented Mar 10, 2017

It is not that much required, just remove them and throw \Exception instead. That should do for you.
Otherwise you might need;
composer require facebook/graph-sdk

@dilipraya
Copy link

dilipraya commented Mar 20, 2017

Thank you for helping out. I am new to symfony and searching for a way to implement social login with my android app.
If you have some time, can you explain how to integrate FOSUserBundle in this solution?

Best Wishes.

@fsaez-ix
Copy link

fsaez-ix commented Oct 6, 2017

@becko you are great! but I have a question, why did you do this?
$user = $this->userProvider->loadUserByOAuthUserResponse($userResponse);

Because if the ios/android app only have a login with facebook, the first time a user doesn't exist in the database.
So in your idea, when are you creating the users?

@pnaoum
Copy link

pnaoum commented Aug 23, 2018

Guys, I'm new to Symfony and PHP.
Where can I add the ResourceOwnerGrantExtension.php file?
And where is the namespace "Acme\Oauth2Bundle\Oauth\Extension" ? is it included in another bundle?
Also what are the end points that I'll call from the mobile app to comlete email registration or Google/Facebook Registration?

@ndusan
Copy link

ndusan commented Oct 22, 2018

@bekco
Are you saying that you're doing calls within JavaScript of your SPA where you hardcode client_id and client_secret?
E.g.

  1. User requested to get signed up/signed in via Facebook (code example: https://developers.facebook.com/docs/facebook-login/web/)
  2. User entered Facebook credentials once asked
  3. Facebook did authResponse and provided
{
    status: 'connected',
    authResponse: {
        accessToken: '...',
        expiresIn:'...',
        reauthorize_required_in:'...'
        signedRequest:'...',
        userID:'...'
    }
}
  1. SPA will make request to API passing Facebook accessToken together with client_id and client_secret
  2. Your solution would then talk again to Facebook and got the user for provided Facebook accessToken and return back to SPA Bearer accessToken

My main concern is that in step 4 (if that's how you do request) in JavaScript (which is on the client side) you would have to hardcode client_id and client_secret and that's no go! Is that what you're doing in step 4 or I got that part wrong?

In general, I'm mainly interested how do you make a call to API from SPA where you have to pass client_id and client_secret and avoid allowing yourself to be hacked. It would be great if you could give me your input on this one.

@nicodemuz
Copy link

@ndusan passing the client secret from the app does sound indeed like a bad approach. Did you find any alternative approach?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment