Created
March 6, 2018 21:35
-
-
Save jackdpeterson/fb82d178e5d64e258fbf9b4150811c63 to your computer and use it in GitHub Desktop.
League OAuth2 OpenID (kind-of) with FaceBook Authorization Code flow to internal Authorization Code grant
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace Sso\Action; | |
use League\OAuth2\Client\Provider\Facebook; | |
use League\OAuth2\Client\Provider\FacebookUser; | |
use League\OAuth2\Server\Entities\ClientEntityInterface; | |
use League\OAuth2\Server\Repositories\ClientRepositoryInterface; | |
use League\OAuth2\Server\RequestTypes\AuthorizationRequest; | |
use Psr\Http\Message\ResponseInterface; | |
use Slim\Http\Request; | |
use Slim\Http\Response; | |
use Sso\AuthorizationServer; | |
use Sso\Entity\Embeddable\ContactInformation; | |
use Sso\Entity\User; | |
use Sso\Repository\UserRepository; | |
use Swagger\Annotations as SWG; | |
/** | |
* Class FacebookAction | |
* @package Sso\Action | |
* @SWG\Get( | |
* path="/v1/login/facebook/{clientId}", | |
* description="Facebook OAuth to Authorization Grant Type", | |
* @SWG\Parameter( | |
* type="string", | |
* in="path", | |
* name="clientId", | |
* required=true, | |
* description="Client ID to redirect back to with a local authorization ?code=X" | |
* ), | |
* @SWG\Parameter( | |
* type="string", | |
* in="query", | |
* name="param", | |
* description="a parameter that can be configured by the requesting client microservice to allow things like a redirect back to a given state." | |
* ) , | |
* @SWG\Response( | |
* response="302", | |
* description="redirects back to client with authorization code in query-string" | |
* ) | |
* ) | |
*/ | |
final class FacebookAction | |
{ | |
private $provider; | |
private $userRepository; | |
private $authServer; | |
private $clientRepository; | |
private $baseURI; | |
public function __construct( | |
Facebook $facebookProvider, | |
UserRepository $userRepository, | |
AuthorizationServer $authorizationServer, | |
ClientRepositoryInterface $clientRepository, | |
string $baseURI | |
) { | |
$this->provider = $facebookProvider; | |
$this->userRepository = $userRepository; | |
$this->authServer = $authorizationServer; | |
$this->clientRepository = $clientRepository; | |
$this->baseURI = $baseURI; | |
} | |
public function __invoke( | |
Request $request, | |
Response $response, | |
array $args | |
): ResponseInterface { | |
$clientId = $args['clientId']; | |
if (is_null($clientId)) { | |
return $response->withJson(['message' => 'The requested Client ID is missing'], 422); | |
} | |
$client = $this->clientRepository->getClientEntity( | |
$clientId, | |
'authorization_code', | |
null, | |
false | |
); | |
if ($client instanceof ClientEntityInterface === false) { | |
return $response->withJson(['message' => 'The requested Client ID is invalid'], 422); | |
} | |
$redirectUri = $this->baseURI . sprintf('login/facebook/%1$s', | |
($args['clientId'] ?? null)); | |
session_start(); | |
if (isset($_GET['param'])) { | |
$_SESSION['param'] = urldecode($_GET['param']); | |
} | |
if (!isset($_GET['code'])) { | |
// If we don't have an authorization code then get one | |
$authUrl = $this->provider->getAuthorizationUrl([ | |
'scope' => ['email',], | |
'redirect_uri' => $redirectUri | |
]); | |
$_SESSION['oauth2state'] = $this->provider->getState(); | |
return $response->withRedirect($authUrl); | |
// Check given state against previously stored one to mitigate CSRF attack | |
} elseif (empty($_GET['state']) || ($_GET['state'] !== $_SESSION['oauth2state'])) { | |
unset($_SESSION['oauth2state']); | |
return $response->withJson([ | |
'message' => 'The provided State ID is invalid. Authentication requires cookies to be enabled.', | |
'detail' => 'No Cross-Site Scripting Forgery prevention token was present or if it was presented ... it did not match what we expect.' | |
], 422); | |
} | |
// Try to get an access token (using the authorization code grant) | |
$token = $this->provider->getAccessToken('authorization_code', [ | |
'code' => $_GET['code'], | |
'redirect_uri' => $redirectUri | |
]); | |
try { | |
/** | |
* @var FacebookUser $FBuser | |
*/ | |
$FBuser = $this->provider->getResourceOwner($token); | |
// perform FBID based lookup | |
$localUserEntity = $this->userRepository->fetchUserByFacebookIdentifier($FBuser->getId()); | |
// look for a user already registered with the same e-mail address | |
if (!$localUserEntity instanceof User) { | |
$localUserEntity = $this->userRepository->fetchUserByEmail($FBuser->getEmail()); | |
} | |
if (!$localUserEntity instanceof User) { | |
$contactInformation = new ContactInformation(); | |
$contactInformation->setFirstName($FBuser->getFirstName()); | |
$contactInformation->setLastName($FBuser->getLastName()); | |
$localUserEntity = $this->userRepository->registerFacebookOAuthUser($FBuser->getId(), | |
$FBuser->getEmail(), | |
$contactInformation); | |
} else { | |
// link the FB user to the existing user by e-mail | |
if (strlen($localUserEntity->getFacebookId()) === 0) { | |
$localUserEntity->setFacebookId($FBuser->getId()); | |
$localUserEntity = $this->userRepository->save($localUserEntity); | |
} | |
} | |
$redirectUri = $client->getRedirectUri(); | |
if ($redirectUri !== null) { | |
if ( | |
is_string($client->getRedirectUri()) | |
&& (strcmp($client->getRedirectUri(), $redirectUri) !== 0) | |
) { | |
return $response->withJson(['message' => 'The requested Client ID does not have a valid Redirect URL registered.'], | |
422); | |
} elseif ( | |
is_array($client->getRedirectUri()) | |
&& in_array($redirectUri, $client->getRedirectUri()) === false | |
) { | |
return $response->withJson(['message' => 'The requested Client ID does not have a valid Redirect URL registered.'], | |
422); | |
} | |
} elseif (is_array($client->getRedirectUri()) && count($client->getRedirectUri()) !== 1 | |
|| empty($client->getRedirectUri()) | |
) { | |
return $response->withJson(['message' => 'The requested Client ID does not have a valid Redirect URL registered.'], | |
422); | |
} | |
$stateParameter = $request->getQueryParam('state'); | |
$authorizationRequest = new AuthorizationRequest(); | |
$authorizationRequest->setGrantTypeId('authorization_code'); | |
$authorizationRequest->setClient($client); | |
$authorizationRequest->setRedirectUri($redirectUri . ($_SESSION['param'] ? '?param=' . urlencode($_SESSION['param']) : null)); | |
$authorizationRequest->setState($stateParameter); | |
$authorizationRequest->setScopes([]); | |
$authorizationRequest->setUser($localUserEntity); | |
$authorizationRequest->setAuthorizationApproved(true); | |
return $this->authServer->completeAuthorizationRequest($authorizationRequest, $response); | |
} catch (\Exception $e) { | |
return $response->withJson([ | |
'message' => 'An internal error has occurred.', | |
'detail' => $e->getMessage() | |
], 500); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment