diff --git a/src/Authkit2.php b/src/Authkit2.php index 424de80..5625e5d 100644 --- a/src/Authkit2.php +++ b/src/Authkit2.php @@ -11,6 +11,10 @@ namespace authkit2; * @method static void cache_set(string $key, mixed $value) * @method static mixed session_get(string $key) * @method static void session_set(string $key, mixed $value) + * @method static void configure(string $client_id, string $client_secret, string $endpoint) + * @method static Oidc\Client get_client() + * @method static Oidc\Token get_token(string $access_token, ?string $refresh_token = null) + * @method static Oidc\Token refresh_token(Oidc\Token $token) */ class Authkit2 { @@ -23,11 +27,16 @@ class Authkit2 /** * Functions this class provides - * * @array */ protected $callbacks = []; + /** + * Oidc client with the application credentials + * @var \authkit2\Oidc\Client + */ + protected $client; + /** * Try and detect if we recognize the environment the library is running * in and adjust our implementations accordingly. @@ -38,14 +47,16 @@ class Authkit2 */ protected function __construct() { + $callbacks = $this->getCommonCallbacks(); if (defined('LARAVEL_START')) { - $this->initializeLaravel(); + $callbacks = array_merge($callbacks, $this->getLaravelCallbacks()); } else { - $this->initializeNative(); + $callbacks = array_merge($callbacks, $this->getNativeCallbacks()); } + $this->callbacks = $callbacks; } /** @@ -126,20 +137,36 @@ class Authkit2 return $value; } + /** + * Initialize common library functions that don't require an environment-specific + * implementation + * + * @return array + */ + protected function getCommonCallbacks(): array + { + return [ + 'cache' => [$this, 'cache_helper'], + 'configure' => [$this, 'ak2_configure'], + 'get_client' => [$this, 'ak2_get_client'], + 'get_token' => [$this, 'ak2_get_token'], + 'refresh_token' => [$this, 'ak2_refresh_token'] + ]; + } + /** * Initialize the class by binding all the PHP native implementations of * functions * - * @return void + * @return array */ - protected function initializeNative(): void + protected function getNativeCallbacks(): array { - $this->callbacks = [ + return [ 'session_get' => [$this, 'native_session_get'], 'session_set' => [$this, 'native_session_set'], 'cache_get' => [$this, 'native_cache_get'], - 'cache_set' => [$this, 'native_cache_set'], - 'cache' => [$this, 'cache_helper'] + 'cache_set' => [$this, 'native_cache_set'] ]; } @@ -147,16 +174,15 @@ class Authkit2 * Initialize the class by binding Laravel adapters as the implementation * of all functions * - * @return void + * @return array */ - protected function initializeLaravel(): void + protected function getLaravelCallbacks(): array { - $this->callbacks = [ + return [ 'session_get' => function(string $key) { return \Session::get($key); }, 'session_set' => function(string $key, $value) { \Session::put($key, $value); }, 'cache_get' => function(string $key) { return \Cache::get($key); }, - 'cache_set' => function(string $key, $value) { \Cache::set($key, $value); }, - 'cache' => [$this, 'cache_helper'] + 'cache_set' => function(string $key, $value) { \Cache::set($key, $value); } ]; } @@ -224,5 +250,24 @@ class Authkit2 { } + protected function ak2_configure(string $client_id, string $client_secret, string $endpoint): void + { + $this->client = new \authkit2\Oidc\Client($endpoint, $client_id, $client_secret); + } + + protected function ak2_get_client(): Oidc\Client + { + return $this->client; + } + + protected function ak2_get_token(string $access_token, ?string $refresh_token = null): Oidc\Token + { + return Oidc\Token::fromString($this->client, $access_token, $refresh_token); + } + + protected function ak2_refresh_token(Oidc\Token $token): Token + { + return $this->client->refreshToken($token); + } } diff --git a/src/Http/Controllers/AuthenticationController.php b/src/Http/Controllers/AuthenticationController.php index 2caa110..378b244 100644 --- a/src/Http/Controllers/AuthenticationController.php +++ b/src/Http/Controllers/AuthenticationController.php @@ -53,7 +53,7 @@ class AuthenticationController extends Controller // TODO: Check for error response // Exchange the code for a token - $token = $this->user_flow->exchangeCodeForToken(config('authkit.authn.openid.redirect_uri'), $request->code); + $token = $this->user_flow->exchangeCodeForToken($request->code, config('authkit.authn.openid.redirect_uri')); $user_info = $token->getUserInfo(); // Try and use the token to find the local user diff --git a/src/Observers/UserObserver.php b/src/Observers/UserObserver.php index 4c34799..691eabc 100644 --- a/src/Observers/UserObserver.php +++ b/src/Observers/UserObserver.php @@ -1,6 +1,7 @@ {$user->getAccessTokenName()}; @@ -21,14 +25,37 @@ class UserObserver $refresh = $user->authkit_refresh_token; } - $user->authkit = \authkit2\Oidc\Token::fromString($token, $refresh); - // TODO: If access_token is expired, try refresh - // If refresh_token is expired, ?!! - // \Illuminate\Auth\Access\UnauthorizedException + // Create a token object + $user->authkit = Authkit2::get_token($token, $refresh); + // Set a refresh callback on the token -- when it's been refreshed, + // then set the new tokens on the user and save it + $user->authkit->setRefreshCallback(function($token) use ($user) { + if ($user instanceof \authkit2\Models\IAuthkitUser) + { + $user->{$user->getAccessTokenName()} = $token->getAccessToken(); + $user->{$user->getRefreshTokenName()} = $token->getRefreshToken(); + } + else + { + $user->authkit_access_token = $token->getAccessToken(); + $user->authkit_refresh_token = $token->getRefreshToken(); + } + $user->save(); + }); } public function saving($user) { + if (isset($user->authkit)) + static::$token_cache[$user->authkit->getAccessToken()] = $user->authkit; unset($user->authkit); } + + public function saved($user) + { + $access_token = ($user instanceof \authkit2\Models\IAuthkitUser) ? $user->{$user->getAccessTokenName()} : $user->authkit_access_token; + if (isset(static::$token_cache[$access_token])) + $user->authkit = static::$token_cache[$access_token]; + + } } diff --git a/src/Oidc/Authentication/Authentication.php b/src/Oidc/Authentication/Authentication.php index 285a18b..379dcdb 100644 --- a/src/Oidc/Authentication/Authentication.php +++ b/src/Oidc/Authentication/Authentication.php @@ -33,4 +33,12 @@ abstract class Authentication }; }; } + + public function getClient(array $options = []): \GuzzleHttp\Client + { + $stack = new \GuzzleHttp\HandlerStack(); + $stack->setHandler(new \GuzzleHttp\Handler\CurlHandler()); + $stack->push($this->getMiddleware()); + return new \GuzzleHttp\Client(array_merge($options, ['handler' => $stack])); + } } diff --git a/src/Oidc/Authentication/TokenAuthentication.php b/src/Oidc/Authentication/TokenAuthentication.php index a822300..19be8d9 100644 --- a/src/Oidc/Authentication/TokenAuthentication.php +++ b/src/Oidc/Authentication/TokenAuthentication.php @@ -15,14 +15,26 @@ class TokenAuthentication extends Authentication */ protected $token; + /** + * Who to call if the token is expired + * @var callable + */ + protected $refresh_callback; + /** * Create a new token authentication provider * * @param Token $token token to authenticate requests with */ - public function __construct(Token $token) + public function __construct(Token $token, callable $refreshCallback = null) { $this->token = $token; + $this->refresh_callback = $refreshCallback; + } + + public function setRefreshCallback(callable $refreshCallback): void + { + $this->refresh_callback = $refreshCallback; } /** @@ -35,6 +47,11 @@ class TokenAuthentication extends Authentication */ public function authenticate(\GuzzleHttp\Psr7\Request $request): \GuzzleHttp\Psr7\Request { + if ($this->token->isExpired() && isset($this->refresh_callback)) + { + $callback = $this->refresh_callback; + $this->token = $callback($this); + } return $request->withHeader('Authorization', 'Bearer '.$this->token->getAccessToken()); } } diff --git a/src/Oidc/Client.php b/src/Oidc/Client.php index 7881173..4e77dd4 100644 --- a/src/Oidc/Client.php +++ b/src/Oidc/Client.php @@ -21,44 +21,53 @@ class Client */ protected $client; + /** + * OAuth client id + * @var string + */ + protected $client_id; + /** * Base url of the OIDC realm * @var string */ - static $oidc_url; + protected $oidc_url; /** * OIDC config fetched from the server or restored from cache * @var array */ - static $oidc_config; + protected $oidc_config; /** - * Create a new OIDC client using the passed authentication provider + * Keys for validating signed JWT tokens + * @var array + */ + protected $oidc_jwks; + + /** + * Create a new OIDC client using the passed in client credentials * - * @param Authentication\Authentication $auth + * @param string $url + * @param string $client_id + * @param string $client_secret */ - public function __construct(Authentication\Authentication $auth) + public function __construct(string $url, string $client_id, string $client_secret) { - $this->auth = $auth; + $this->auth = new Authentication\ClientAuthentication($client_id, $client_secret); + $this->client = $this->auth->getClient(); + $this->oidc_url = $url; + $this->oidc_config = null; + $this->client_id = $client_id; } /** * Retrieve a HTTP client containing our authentication middleware * - * Constructed on first use - * * @return \GuzzleHttp\Client */ public function getClient(): \GuzzleHttp\Client { - if (!isset($this->client)) - { - $stack = new \GuzzleHttp\HandlerStack(); - $stack->setHandler(new \GuzzleHttp\Handler\CurlHandler()); - $stack->push($this->auth->getMiddleware()); - $this->client = new \GuzzleHttp\Client(['handler' => $stack]); - } return $this->client; } @@ -67,101 +76,79 @@ class Client * * @return ?string */ - public static function getUrl(): ?string + public function getUrl(): string { - return static::$oidc_url; + return $this->oidc_url; } /** - * Configure the library with a OpenId Connect realm url + * Get the OpenId Connect configuration * - * @param string $url - * @return void + * @return array */ - public static function setUrl(string $url): void + public function getConfiguration(): array { - static::$oidc_url = $url; - } + if (!isset($this->oidc_config)) + { + $url = $this->oidc_url; + $this->oidc_config = Authkit2::cache('oidc.config.'.md5($this->oidc_url), function() use ($url) { + $response = (new \GuzzleHttp\Client())->get($url.'/.well-known/openid-configuration'); + return json_decode($response->getBody(), true); + }); - /** - * Set the OpenId Connect configuration - * - * This is provided to allow external caching rather than having us refetch - * the configuration on every invocation of the project. - * - * @param array $config config as retrieved from getOidcConfig() - * @return void - */ - public static function setOidcConfig(array $config): void - { - static::$oidc_config = $config; + } + return $this->oidc_config; } /** - * Get the OpenId Connect configuration + * Get the web key set for verifying JWTs * - * If not restored/set via setOidcConfig, this will be fetched from the OIDC - * realm on first use - * - * @return array + * @return array */ - public static function getOidcConfig(): array + public function getJsonWebKeySet(): array { - if (!isset(static::$oidc_config)) + if (!isset($this->oidc_jwks)) { - static::$oidc_config = Authkit2::cache('oidc.config', function() { - $response = (new \GuzzleHttp\Client())->get(static::$oidc_url.'/.well-known/openid-configuration'); - return json_decode($response->getBody(), true); + $client = $this; + $this->oidc_jwks = Authkit2::cache('oidc.config.'.md5($this->oidc_url).'.jwks', function() use ($client) { + $response = $client->get($client->getConfiguration()['jwks_uri']); + return json_decode(json_encode($response), true); }); - } - return static::$oidc_config; + return $this->oidc_jwks; } /** - * Fetch a specific OpenId Connect endpoint from the configuration + * Get the signing algorithms for signing JWTs * - * @param string $endpoint_name - * @return string + * @return string[] */ - public function getEndpointUrl(string $endpoint_name): string + public function getTokenSigningAlgorithms(): array { - return static::getOidcConfig()[$endpoint_name.'_endpoint']; + return $this->getConfiguration()['id_token_signing_alg_values_supported']; } /** - * Determine the final URL to make a request to given an arbitrarily-defined - * 'endpoint'. - * - * If the passed in value is a valid URI, it will be used directly. Otherwise, - * we will attempt to find a configured endpoint with a name matching the - * passed in value in the OIDC config. + * Fetch a specific OpenId Connect endpoint from the configuration * - * @param string $endpoint url or endpoint name - * @return string url to call + * @param string $endpoint_name + * @return string */ - protected function parseEndpoint(string $endpoint): string + public function getEndpointUrl(string $endpoint_name): string { - if (filter_var($endpoint, \FILTER_VALIDATE_URL)) - { - return $endpoint; - } - else - { - return $this->getEndpointUrl($endpoint); - } + return $this->getConfiguration()[$endpoint_name.'_endpoint']; } /** * Make a HTTP get request to a OIDC endpoint or other URL * - * @param string $endpoint url or endpoint name + * @param string $url * @param array $params query string parameters * @return object json decoded response */ - public function get(string $endpoint, array $params = []): object + protected function get(string $url, array $params = []): object { - $response = $this->getClient()->get($this->parseEndpoint($endpoint), [ + $response = $this->getClient()->get($url, [ 'query' => $params ]); return json_decode($response->getBody()); @@ -173,15 +160,94 @@ class Client * If form parameters are provided the request is sent as * application/x-www-form-urlencoded * - * @param string $endpoint url or endpoint name + * @param string $url * @param array $params form fields * @return object json decoded response */ - public function post(string $endpoint, array $params = []): object + protected function post(string $url, array $params = []): object { - $response = $this->getClient()->post($this->parseEndpoint($endpoint), [ + $response = $this->getClient()->post($url, [ 'form_params' => $params ]); return json_decode($response->getBody()); } + + /** + * Create a 'service account' token tied to this client's id + * + * @return Token + */ + public function createTokenFromClient(): Token + { + $response = $this->post($this->getEndpointUrl('token'), [ + 'grant_type' => 'client_credentials' + ]); + return Token::fromResponse($this, $response); + } + + /** + * Convert a returned authorization code from the three legged flow + * into a token + * + * @param string $code + * @param string $redirect_uri + * @return Token + */ + public function createTokenFromAuthorizationCode(string $code, string $redirect_uri): Token + { + $response = $this->post($this->getEndpointUrl('token'), [ + 'grant_type' => 'authorization_code', + 'code' => $code, + 'redirect_uri' => $redirect_uri + ]); + // todo: check for response->error, response->error_description + return Token::fromResponse($this, $response); + } + + /** + * Create a new access token from a refresh token + * + * @param string $refresh_token + * @return Token + */ + public function createTokenFromRefreshToken(string $refresh_token): Token + { + $response = $this->post($this->getEndpointUrl('token'), [ + 'grant_type' => 'refresh_token', + 'refresh_token' => $refresh_token + ]); + return Token::fromResponse($this, $response); + } + + public function createAuthorizationRedirectUrl(string $redirect_uri, array $scopes, string $state): string + { + return $this->getEndpointUrl('authorization').'?'.http_build_query([ + 'client_id' => $this->client_id, + 'redirect_uri' => $redirect_uri, + 'scope' => implode(',', $scopes), + 'response_type' => 'code', + 'state' => $state + ]); + } + + public function createLogoutUrl(string $redirect_uri): string + { + return $this->getEndpointUrl('end_session').'?'.http_build_query([ + 'redirect_uri' => $redirect_uri + ]); + } + + public function refreshToken(Token $token): Token + { + return $this->createTokenFromRefreshToken($token->getRefreshToken()); + } + + public function getTokenUserInfo(Token $token): array + { + return json_decode($token->getClient()->get($this->getEndpointUrl('userinfo'))->getBody(), true); + } + + // todo: introspect, etc + + } diff --git a/src/Oidc/Flows/ServiceAccountFlow.php b/src/Oidc/Flows/ServiceAccountFlow.php index 7459650..bc74e10 100644 --- a/src/Oidc/Flows/ServiceAccountFlow.php +++ b/src/Oidc/Flows/ServiceAccountFlow.php @@ -19,12 +19,11 @@ class ServiceAccountFlow /** * Initialize a new service account flow * - * @param string $client_id oauth client id - * @param string $client_secret oauth client secret + * @param Client $client */ - public function __construct(string $client_id, string $client_secret) + public function __construct(Client $client) { - $this->client = new Client(new ClientAuthentication($client_id, $client_secret)); + $this->client = $client; } /** @@ -34,8 +33,7 @@ class ServiceAccountFlow */ public function getServiceAccountToken(): Token { - $response = $this->client->post('token', ['grant_type'=>'client_credentials']); - return Token::fromResponse($response); + return $this->client->createTokenFromClient(); } } diff --git a/src/Oidc/Flows/UserFlow.php b/src/Oidc/Flows/UserFlow.php index 530e0fc..012a8c1 100644 --- a/src/Oidc/Flows/UserFlow.php +++ b/src/Oidc/Flows/UserFlow.php @@ -12,32 +12,19 @@ use authkit2\Oidc\Token; class UserFlow { /** - * OIDC client authenticating as the client + * OIDC 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 + * @param Client $client */ - public function __construct(string $client_id, string $client_secret) + public function __construct(Client $client) { - $this->client_id = $client_id; - $this->client_secret = $client_secret; - $this->client = new Client(new ClientAuthentication($client_id, $client_secret)); + $this->client = $client; } /** @@ -57,14 +44,7 @@ class UserFlow $states = Authkit2::session_get('userflow.state') ?? []; array_push($states, $state); Authkit2::session_set('userflow.state', $states); - - 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 - ]); + return $this->client->createAuthorizationRedirectUrl($redirect_uri, $scopes, $state); } /** @@ -92,19 +72,15 @@ class UserFlow * 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 $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 + public function exchangeCodeForToken(string $code, string $redirect_uri): Token { - $response = $this->client->post('token', [ - 'grant_type' => 'authorization_code', - 'code' => $code, - 'redirect_uri' => $redirect_uri - ]); - - return Token::fromResponse($response); + return $this->client->createTokenFromAuthorizationCode($code, $redirect_uri); } /** @@ -116,9 +92,7 @@ class UserFlow */ public function getLogoutUrl(string $redirect_uri): string { - return $this->client->getEndpointUrl('end_session').'?'.http_build_query([ - 'redirect_uri' => $redirect_uri - ]); + return $this->client->createLogoutUrl($redirect_uri); } } diff --git a/src/Oidc/Token.php b/src/Oidc/Token.php index 64fcf78..15a139d 100644 --- a/src/Oidc/Token.php +++ b/src/Oidc/Token.php @@ -12,16 +12,8 @@ use Firebase\JWT\JWK; 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 + * OIDC client + * @var Client */ protected $client = null; @@ -41,13 +33,25 @@ class Token * Cache of userinfo endpoint response * @var array */ - protected $userinfo = null; + protected $user_info = null; + + /** + * Decoded access token JWT data + * @var array + */ + protected $access_token_data = null; /** - * Cache of decoded JWT token + * Decoded refresh token JWT data * @var array */ - protected $token_data = null; + protected $refresh_token_data = null; + + /** + * Callback to be notified when this token is refreshed + * @var callable + */ + protected $refresh_callback; /** * Initialize token with the from*() static methods @@ -60,76 +64,167 @@ class Token * Create a token given a access_token and optionally refresh_token, passed * as a string * + * @param Client $client * @param string $access_token * @param ?string $refresh_token * @return Token */ - public static function fromString(string $access_token, ?string $refresh_token = null): Token + public static function fromString(Client $client, string $access_token, ?string $refresh_token = null): Token { $token = new Token(); + $token->client = $client; $token->access_token = $access_token; $token->refresh_token = $refresh_token; return $token; } + /** + * Get a HTTP client that's authenticated with this token's credentials + * + * @param array $options + * @return \GuzzleHttp\Client + */ + public function getClient(array $options = []): \GuzzleHttp\Client + { + // Create the token auth implementation; this holds a callback to the token + // to ask it to refresh itself + $state = new \stdClass(); + + $state->refresher = function($token) use ($state) { + $client = $this->client; + $refresh_callback = $this->refresh_callback; + + // Refresh the token + $new_token = $client->createTokenFromRefreshToken($this->refresh_token); + + // Rebind this callback to the new token + $state->refresher->bindTo($new_token); + + // Call the refresh callback + if (isset($refresh_callback)) + { + // Copy over the token-level refresh callback + $new_token->setRefreshCallback($refresh_callback); + $refresh_callback($new_token); + } + + return $new_token; + }; + + $auth = new Authentication\TokenAuthentication($this, $state->refresher); + return $auth->getClient($options); + } + + /** + * Callback to notify when this token is refreshed + * + * @param callable $callback + * @return void + */ + public function setRefreshCallback(callable $callback): void + { + $this->refresh_callback = $callback; + } + /** * Create a token from a OIDC response from the token endpoint * + * @param Client $client * @param object $response * @return Token */ - public static function fromResponse(object $response): Token + public static function fromResponse(Client $client, object $response): Token { $token = new Token(); + $token->client = $client; $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 + * Fetch the raw decoded data out of our JWT access token * - * @return Client + * @return array */ - public function getClient(): Client + public function getAccessTokenData(): array { - if (!isset($this->client)) + if (!isset($this->access_token_data)) { - $this->client = new Client(new Authentication\TokenAuthentication($this)); + $this->access_token_data = json_decode(json_encode($this->decode($this->access_token)), true); } - return $this->client; + return $this->access_token_data; } /** - * Refresh the access token + * Fetch the raw decoded data out of our JWT refresh token * - * @return void + * @return array */ - public function refresh() + public function getRefreshTokenData(): array { + if (!isset($this->refresh_token_data)) + { + if (!isset($this->refresh_token)) + { + throw new \UnexpectedValueException("Refresh token not set!"); + } + $this->refresh_token_data = json_decode(json_encode($this->decode($this->refresh_token)), true); + } + return $this->token_data; } /** - * Fetch the raw decoded data out of our JWT access token + * Decode a token as a JWT token * - * @return array + * @param string $token + * @return object + */ + protected function decode(string $token): object + { + return JWT::decode($token, JWK::parseKeySet($this->client->getJsonWebKeySet()), $this->client->getTokenSigningAlgorithms()); + } + + /** + * Check whether the token is valid -- that is, whether you could actually + * use it for things. + * + * To be considered valid, the access token must be parseable and signed by + * the correct keys but _may_ be expired. The refresh token must be parseable, + * signed by the correct key, and may not be expired. + * + * @return bool */ - public function getTokenData(): array + public function isValid(): bool { - if (!isset($this->token_data)) + try { - $this->token_data = json_decode(json_encode($this->decode()), true); + $this->getAccessTokenData(); + return true; + } + catch (\UnexpectedValueException $ex) + { + if ($ex instanceof \Firebase\JWT\ExpiredException) + { + try + { + $this->getRefreshTokenData(); + return true; + } + catch (\UnexpectedValueException $ex) + { + return false; + } + } + return false; } - 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. + * As long as the refresh token is valid, this is recoverly by calling + * passing this token to refresh on the client. * * @return bool */ @@ -137,31 +232,25 @@ class Token { try { - $data = $this->getTokenData(); + $token_data = $this->getAccessTokenData(); + if ($token_data['exp'] <= time()) + return true; + return false; } 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 + * Check whether this token needs a refresh to be used * - * @return object + * @return bool */ - protected function decode(): object + public function needsRefresh(): bool { - $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']); + return $this->isValid() && $this->isExpired() && isset($this->refresh_token); } /** @@ -192,12 +281,7 @@ class Token */ public function getUserInfo(): array { - if (!isset($this->userinfo)) - { - $this->userinfo = json_decode(json_encode($this->getClient()->get('userinfo')), true); - } - - return $this->userinfo; + return $this->client->getTokenUserInfo($this); } /** @@ -217,7 +301,7 @@ class Token */ public function getUserId(): string { - return 'crn:user:'.$this->getTokenData()['sub']; + return 'crn:user:'.$this->getAccessTokenData()['sub']; } } diff --git a/src/Providers/Authkit2ServiceProvider.php b/src/Providers/Authkit2ServiceProvider.php index e0aeeee..82a4017 100644 --- a/src/Providers/Authkit2ServiceProvider.php +++ b/src/Providers/Authkit2ServiceProvider.php @@ -48,10 +48,6 @@ class Authkit2ServiceProvider extends ServiceProvider __DIR__.'/../../database/migrations/existing/authkit2_users_update_minimal.php' => database_path('migrations/'.date('Y_m_d_His').'_authkit2_users_update_minimal.php') ], 'migrations_existing'); } - - $this->app->booted(function($app) { - \authkit2\Oidc\Client::setUrl(config('authkit.authn.openid.endpoint')); - }); } } diff --git a/src/Providers/AuthnServiceProvider.php b/src/Providers/AuthnServiceProvider.php index cd1bc1f..46b340f 100644 --- a/src/Providers/AuthnServiceProvider.php +++ b/src/Providers/AuthnServiceProvider.php @@ -5,6 +5,7 @@ declare(strict_types=1); namespace authkit2\Providers; use Illuminate\Support\ServiceProvider; +use \authkit2\Authkit2; /** * Authentication provider to perform setup for authentication processes @@ -19,10 +20,10 @@ class AuthnServiceProvider extends ServiceProvider public function register(): void { $this->app->singleton(\authkit2\Oidc\Flows\ServiceAccountFlow::class, function($app) { - return new \authkit2\Oidc\Flows\ServiceAccountFlow(config('authkit.authn.openid.client_id'), config('authkit.authn.openid.client_secret')); + return new \authkit2\Oidc\Flows\ServiceAccountFlow(Authkit2::get_client()); }); $this->app->singleton(\authkit2\Oidc\Flows\UserFlow::class, function($app) { - return new \authkit2\Oidc\Flows\UserFlow(config('authkit.authn.openid.client_id'), config('authkit.authn.openid.client_secret')); + return new \authkit2\Oidc\Flows\UserFlow(Authkit2::get_client()); }); } @@ -118,5 +119,9 @@ class AuthnServiceProvider extends ServiceProvider 'authkit.authn.openid.endpoint' => env('AUTHKIT_ENDPOINT', isset($config_json['auth-server-url']) && isset($config_json['realm']) ? $config_json['auth-server-url'].'realms/'.$config_json['realm'] : null) ]); } + + $this->app->booted(function($app) { + Authkit2::configure(config('authkit.authn.openid.client_id'), config('authkit.authn.openid.client_secret'), config('authkit.authn.openid.endpoint')); + }); } }