You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
125 lines
3.4 KiB
125 lines
3.4 KiB
3 years ago
|
<?php
|
||
|
|
||
|
namespace authkit2\Oidc\Flows;
|
||
|
use authkit2\Oidc\Client;
|
||
|
use authkit2\Oidc\Authentication\ClientAuthentication;
|
||
|
use authkit2\Oidc\Token;
|
||
|
|
||
|
/**
|
||
|
* Oauth three legged auth flow for authenticating users
|
||
|
*/
|
||
|
class UserFlow
|
||
|
{
|
||
|
/**
|
||
|
* OIDC client authenticating as the client
|
||
|
* @var Client
|
||
|
*/
|
||
|
protected $client;
|
||
|
/**
|
||
|
* oauth client id
|
||
|
* @var string
|
||
|
*/
|
||
|
protected $client_id;
|
||
|
/**
|
||
|
* oauth client secret
|
||
|
* @var string
|
||
|
*/
|
||
|
protected $client_secret;
|
||
|
|
||
|
/**
|
||
|
* Initialize a new user login flow
|
||
|
*
|
||
|
* @param string $client_id oauth client id
|
||
|
* @param string $client_secret oauth client secret
|
||
|
*/
|
||
|
public function __construct(string $client_id, string $client_secret)
|
||
|
{
|
||
|
$this->client_id = $client_id;
|
||
|
$this->client_secret = $client_secret;
|
||
|
$this->client = new Client(new ClientAuthentication($client_id, $client_secret));
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get the URL the user should be redirected to to begin the authorization process
|
||
|
*
|
||
|
* @param string $redirect_uri url the oidc endpoint should redirect the user back to; generally must be authorized in the provider
|
||
|
* @param string[] $scopes the scopes to request in the token
|
||
|
* @return string url to redirect the user to
|
||
|
*/
|
||
|
public function getRedirectUrl(string $redirect_uri, array $scopes = ['email']): string
|
||
|
{
|
||
|
// Generate nonce
|
||
|
// We use 30 because that should generate an even number of base64 characters
|
||
|
// and not require padding. (6 bits / char)
|
||
|
$state = base64_encode(random_bytes(30));
|
||
|
$this->prepareSession();
|
||
|
// Keep a list of all valid states we've generated
|
||
|
array_push($_SESSION['authkit2.state'], $state);
|
||
|
|
||
|
return $this->client->getEndpointUrl('authorization').'?'.http_build_query([
|
||
|
'client_id' => $this->client_id,
|
||
|
'redirect_uri' => $redirect_uri,
|
||
|
'scope' => implode(',', $scopes),
|
||
|
'response_type' => 'code',
|
||
|
'state' => $state
|
||
|
]);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Validate that the provided state string is one we have previously generated
|
||
|
*
|
||
|
* @throws \Exception if the state is unrecognized or invalid
|
||
|
* @return void
|
||
|
*/
|
||
|
public function validateState(string $state): void
|
||
|
{
|
||
|
$this->prepareSession();
|
||
|
for ($i=0; $i<sizeof($_SESSION['authkit2.state']); $i++)
|
||
|
{
|
||
|
if ($_SESSION['authkit2.state'][$i] == $state)
|
||
|
{
|
||
|
$_SESSION['authkit2.state'] = array_splice($_SESSION['authkit2.state'], $i, 1);
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
throw new \Exception("Invalid auth nonce");
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* After the user is redirected back with a authorization code, exchange it
|
||
|
* for an access token
|
||
|
*
|
||
|
* @param string $redirect_uri url the oidc endpoint redirects back to; must match one given in call to getRedirectUrl
|
||
|
* @param string $code code returned by the authorization flow
|
||
|
* @return Token
|
||
|
*/
|
||
|
public function exchangeCodeForToken(string $redirect_uri, string $code): Token
|
||
|
{
|
||
|
$response = $this->client->post('token', [
|
||
|
'grant_type' => 'authorization_code',
|
||
|
'code' => $code,
|
||
|
'redirect_uri' => $redirect_uri
|
||
|
]);
|
||
|
|
||
|
return Token::fromResponse($response);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Ensure that a PHP session exists for storing the state information in
|
||
|
*
|
||
|
* @return void
|
||
|
*/
|
||
|
protected function prepareSession(): void
|
||
|
{
|
||
|
// If a session doesn't exist, start one
|
||
|
// TODO: PHP_SESSION_DISABLED shouldn't happen in any sane universe, but
|
||
|
// throw an exception if that's the case
|
||
|
if (session_status() == \PHP_SESSION_NONE)
|
||
|
session_start();
|
||
|
if (!isset($_SESSION['authkit2.state']))
|
||
|
$_SESSION['authkit2.state'] = [];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|