Skip to content

Instantly share code, notes, and snippets.

@bekco
Last active May 8, 2019 06:36
Show Gist options
  • 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' }
@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