-
-
Save bekco/c14252d616b3bc17d5d2dd64fd543669 to your computer and use it in GitHub Desktop.
<?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' } |
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.
@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?
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?
@bekco
Are you saying that you're doing calls within JavaScript of your SPA where you hardcode client_id and client_secret?
E.g.
- User requested to get signed up/signed in via Facebook (code example: https://developers.facebook.com/docs/facebook-login/web/)
- User entered Facebook credentials once asked
- Facebook did authResponse and provided
{
status: 'connected',
authResponse: {
accessToken: '...',
expiresIn:'...',
reauthorize_required_in:'...'
signedRequest:'...',
userID:'...'
}
}
- SPA will make request to API passing Facebook accessToken together with client_id and client_secret
- 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.
@ndusan passing the client secret from the app does sound indeed like a bad approach. Did you find any alternative approach?
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