Skip to content

Instantly share code, notes, and snippets.

@jackdpeterson
Created March 6, 2018 21:35
Show Gist options
  • Save jackdpeterson/fb82d178e5d64e258fbf9b4150811c63 to your computer and use it in GitHub Desktop.
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
<?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