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.

223 lines
4.5 KiB

<?php
namespace authkit2\Oidc;
use authkit2\Authkit2;
use authkit2\Oidc\Client;
use Firebase\JWT\JWT;
use Firebase\JWT\JWK;
/**
* A OpenId Connect token, optionally with a refresh token
*/
class Token
{
/**
* If we check for an expired token and it expires within this many seconds
* from now, just go ahead and refresh it early
* @var int
*/
const EXPIRATION_GRACE_PERIOD = 60;
/**
* Http client we've initialized with our authentication middleware for this
* token in place
* @var \GuzzleHttp\Client
*/
protected $client = null;
/**
* OIDC JWT access token
* @var string
*/
protected $access_token;
/**
* OIDC JWT refresh token
* @var string
*/
protected $refresh_token;
/**
* Cache of userinfo endpoint response
* @var array<string,scalar>
*/
protected $userinfo = null;
/**
* Cache of decoded JWT token
* @var array<string,mixed>
*/
protected $token_data = null;
/**
* Initialize token with the from*() static methods
*/
protected function __construct()
{
}
/**
* Create a token given a access_token and optionally refresh_token, passed
* as a string
*
* @param string $access_token
* @param ?string $refresh_token
* @return Token
*/
public static function fromString(string $access_token, ?string $refresh_token = null): Token
{
$token = new Token();
$token->access_token = $access_token;
$token->refresh_token = $refresh_token;
return $token;
}
/**
* Create a token from a OIDC response from the token endpoint
*
* @param object $response
* @return Token
*/
public static function fromResponse(object $response): Token
{
$token = new Token();
$token->access_token = $response->access_token ?? null;
$token->access_token_expires_at = isset($response->expires_in) ? time() + $response->expires_in : null;
$token->refresh_token = $response->refresh_token ?? null;
$token->refresh_token_expires_at = isset($response->refresh_expires_in) ? time() + $response->refresh_expires_in : null;
return $token;
}
/**
* Fetch an oidc client authenticated with this token
*
* @return Client
*/
public function getClient(): Client
{
if (!isset($this->client))
{
$this->client = new Client(new Authentication\TokenAuthentication($this));
}
return $this->client;
}
/**
* Refresh the access token
*
* @return void
*/
public function refresh()
{
}
/**
* Fetch the raw decoded data out of our JWT access token
*
* @return array<string,mixed>
*/
public function getTokenData(): array
{
if (!isset($this->token_data))
{
$this->token_data = json_decode(json_encode($this->decode()), true);
}
return $this->token_data;
}
/**
* Check whether the access token is expired
*
* If we fail to parse it because it's expired, or the expiration time is
* within EXPIRATION_GRACE_PERIOD seconds of now, we consider it expired.
*
* @return bool
*/
public function isExpired(): bool
{
try
{
$data = $this->getTokenData();
}
catch (\Firebase\JWT\ExpiredException $ex)
{
return true;
}
if ($data['exp'] <= time() - static::EXPIRATION_GRACE_PERIOD)
return true;
else
return false;
}
/**
* Decode the access token as a JWT token
*
* @return object
*/
protected function decode(): object
{
$client = $this->getClient();
$jwks = Authkit2::cache('oidc.jwks', function() use ($client) {
$response = $client->get(Client::getOidcConfig()['jwks_uri']);
return json_decode(json_encode($response), true);
});
return JWT::decode($this->access_token, JWK::parseKeySet($jwks), Client::getOidcConfig()['id_token_signing_alg_values_supported']);
}
/**
* Fetch the underlying access token this token represents
*
* @return string
*/
public function getAccessToken(): string
{
return $this->access_token;
}
/**
* Fetch the user's refresh token
*
* @return string
*/
public function getRefreshToken(): string
{
return $this->refresh_token;
}
/**
* Fetch the user info associated with this token from the OIDC
* provider
*
* @return array<string,scalar>
*/
public function getUserInfo(): array
{
if (!isset($this->userinfo))
{
$this->userinfo = json_decode(json_encode($this->getClient()->get('userinfo')), true);
}
return $this->userinfo;
}
/**
* Fetch the roles encoded in this token
*
* @return string[]
*/
public function getRoles(): array
{
return $this->getTokenData()['realm_access']['roles'];
}
/**
* Fetch the uuid encoded in this token
*
* @return string
*/
public function getUserId(): string
{
return 'crn:user:'.$this->getTokenData()['sub'];
}
}