|
|
|
<?php
|
|
|
|
|
|
|
|
declare(strict_types=1);
|
|
|
|
|
|
|
|
namespace authkit2\Oidc\Flows;
|
|
|
|
|
|
|
|
use authkit2\Authkit2;
|
|
|
|
use authkit2\Oidc\Client;
|
|
|
|
use authkit2\Oidc\Token;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Oauth three legged auth flow for authenticating users.
|
|
|
|
*/
|
|
|
|
class UserFlow
|
|
|
|
{
|
|
|
|
/**
|
|
|
|
* OIDC client.
|
|
|
|
* @var Client
|
|
|
|
*/
|
|
|
|
protected $client;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Initialize a new user login flow.
|
|
|
|
*
|
|
|
|
* @param Client $client
|
|
|
|
*/
|
|
|
|
public function __construct(Client $client)
|
|
|
|
{
|
|
|
|
$this->client = $client;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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));
|
|
|
|
// Keep a list of all valid states we've generated
|
|
|
|
$states = Authkit2::session_get('userflow.state') ?? [];
|
|
|
|
array_push($states, $state);
|
|
|
|
Authkit2::session_set('userflow.state', $states);
|
|
|
|
return $this->client->createAuthorizationRedirectUrl($redirect_uri, $scopes, $state);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Validate that the provided state string is one we have previously generated.
|
|
|
|
*
|
|
|
|
* @param string $state
|
|
|
|
* @throws \Exception if the state is unrecognized or invalid
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public function validateState(string $state): void
|
|
|
|
{
|
|
|
|
$states = Authkit2::session_get('userflow.state') ?? [];
|
|
|
|
for ($i = 0; $i < sizeof($states); $i++)
|
|
|
|
{
|
|
|
|
if ($states[$i] == $state)
|
|
|
|
{
|
|
|
|
$states = array_splice($states, $i, 1);
|
|
|
|
Authkit2::session_set('userflow.state', $states);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
throw new \Exception('Invalid auth nonce');
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* After the user is redirected back with a authorization code, exchange it
|
|
|
|
* for an access token.
|
|
|
|
*
|
|
|
|
* THIS DOES NOT VALIDATE THE STATE. Call validateState first.
|
|
|
|
*
|
|
|
|
* @param string $code code returned by the authorization flow
|
|
|
|
* @param string $redirect_uri url the oidc endpoint redirects back to; must match one given in call to getRedirectUrl
|
|
|
|
* @return Token
|
|
|
|
*/
|
|
|
|
public function exchangeCodeForToken(string $code, string $redirect_uri): Token
|
|
|
|
{
|
|
|
|
return $this->client->createTokenFromAuthorizationCode($code, $redirect_uri);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* If we want to log out of the SSO service and all apps, the URL to hit to
|
|
|
|
* sign out everywhere.
|
|
|
|
*
|
|
|
|
* @param string $redirect_uri url to redirect back to after logout completes
|
|
|
|
* @return string
|
|
|
|
*/
|
|
|
|
public function getLogoutUrl(string $redirect_uri): string
|
|
|
|
{
|
|
|
|
return $this->client->createLogoutUrl($redirect_uri);
|
|
|
|
}
|
|
|
|
}
|