diff --git a/src/OpenIDConnectClient.php b/src/OpenIDConnectClient.php index e12ad9c6..86efcd58 100644 --- a/src/OpenIDConnectClient.php +++ b/src/OpenIDConnectClient.php @@ -291,133 +291,152 @@ public function setIssuer($issuer) { public function setResponseTypes($response_types) { $this->responseTypes = array_merge($this->responseTypes, (array)$response_types); } + + /** + * use this method to magically handle all incoming OIDC requests at one endpoint + * if you need more control, use the handleError(), handleCode(), handleClaims(), requestAuthorisation() and redirect() methods + * @return bool + * @throws OpenIDConnectClientException + */ + public function authenticate(): bool { + // Do a preemptive check to see if the provider has thrown an error from a previous redirect + if (isset($_REQUEST['error'])) { + // always throws an exception, routine will end here + $this->handleError($_REQUEST['error'], $_REQUEST['error_description'] ?? null); + } + + // If we have an authorization code then proceed to request a token + if (isset($_REQUEST['code'])) { + return $this->handleCode($_REQUEST['code']); + } + + if ($this->allowImplicitFlow && isset($_REQUEST['id_token'])) { + return $this->handleClaims($_REQUEST['id_token'], $_REQUEST['access_token'] ?? null, $_REQUEST['state'] ?? null); + } + + $auth_endpoint = $this->requestAuthorization(); + $this->redirect($auth_endpoint); + + return false; + } + + /** + * @throws OpenIDConnectClientException + */ + public function handleError(string $error, string $errorDescription = null) { + $desc = $errorDescription !== null ? ' Description: '.$errorDescription : ''; + throw new OpenIDConnectClientException('Error: '.$error.$desc); + } + + /** + * @throws OpenIDConnectClientException + */ + public function handleCode(string $code): bool { + $token_json = $this->requestTokens($code); + + // Throw an error if the server returns one + if (isset($token_json->error)) { + if (isset($token_json->error_description)) { + throw new OpenIDConnectClientException($token_json->error_description); + } + throw new OpenIDConnectClientException('Got response: '.$token_json->error); + } + + // Do an OpenID Connect session check + if (!isset($_REQUEST['state']) || ($_REQUEST['state'] !== $this->getState())) { + throw new OpenIDConnectClientException('Unable to determine state'); + } + + // Cleanup state + $this->unsetState(); + + if (!property_exists($token_json, 'id_token')) { + throw new OpenIDConnectClientException('User did not authorize openid scope.'); + } + + $id_token = $token_json->id_token; + $idTokenHeaders = $this->decodeJWT($id_token); + if (isset($idTokenHeaders->enc)) { + // Handle JWE + $id_token = $this->handleJweResponse($id_token); + } + + $claims = $this->decodeJWT($id_token, 1); + + // Verify the signature + $this->verifySignatures($id_token); + + // Save the id token + $this->idToken = $id_token; + + // Save the access token + $this->accessToken = $token_json->access_token; + + // If this is a valid claim + if ($this->verifyJWTClaims($claims, $token_json->access_token)) { + + // Clean up the session a little + $this->unsetNonce(); + + // Save the full response + $this->tokenResponse = $token_json; + + // Save the verified claims + $this->verifiedClaims = $claims; + + // Save the refresh token, if we got one + if (isset($token_json->refresh_token)) { + $this->refreshToken = $token_json->refresh_token; + } + + // Success! + return true; + } + + throw new OpenIDConnectClientException ('Unable to verify JWT claims'); + } /** - * @return bool * @throws OpenIDConnectClientException */ - public function authenticate(): bool + public function handleClaims(string $id_token, string $accessToken = null, string $state = null): bool { - // Do a preemptive check to see if the provider has thrown an error from a previous redirect - if (isset($_REQUEST['error'])) { - $desc = isset($_REQUEST['error_description']) ? ' Description: ' . $_REQUEST['error_description'] : ''; - throw new OpenIDConnectClientException('Error: ' . $_REQUEST['error'] .$desc); + // Do an OpenID Connect session check + if ($state === null || ($state !== $this->getState())) { + throw new OpenIDConnectClientException('Unable to determine state'); } - // If we have an authorization code then proceed to request a token - if (isset($_REQUEST['code'])) { - - $code = $_REQUEST['code']; - $token_json = $this->requestTokens($code); - - // Throw an error if the server returns one - if (isset($token_json->error)) { - if (isset($token_json->error_description)) { - throw new OpenIDConnectClientException($token_json->error_description); - } - throw new OpenIDConnectClientException('Got response: ' . $token_json->error); - } - - // Do an OpenID Connect session check - if (!isset($_REQUEST['state']) || ($_REQUEST['state'] !== $this->getState())) { - throw new OpenIDConnectClientException('Unable to determine state'); - } + // Cleanup state + $this->unsetState(); - // Cleanup state - $this->unsetState(); + $claims = $this->decodeJWT($id_token, 1); - if (!property_exists($token_json, 'id_token')) { - throw new OpenIDConnectClientException('User did not authorize openid scope.'); - } + // Verify the signature + $this->verifySignatures($id_token); - $id_token = $token_json->id_token; - $idTokenHeaders = $this->decodeJWT($id_token); - if (isset($idTokenHeaders->enc)) { - // Handle JWE - $id_token = $this->handleJweResponse($id_token); - } + // Save the id token + $this->idToken = $id_token; - $claims = $this->decodeJWT($id_token, 1); + // If this is a valid claim + if ($this->verifyJWTClaims($claims, $accessToken)) { - // Verify the signature - $this->verifySignatures($id_token); + // Clean up the session a little + $this->unsetNonce(); - // Save the id token - $this->idToken = $id_token; + // Save the verified claims + $this->verifiedClaims = $claims; // Save the access token - $this->accessToken = $token_json->access_token; - - // If this is a valid claim - if ($this->verifyJWTClaims($claims, $token_json->access_token)) { - - // Clean up the session a little - $this->unsetNonce(); - - // Save the full response - $this->tokenResponse = $token_json; - - // Save the verified claims - $this->verifiedClaims = $claims; - - // Save the refresh token, if we got one - if (isset($token_json->refresh_token)) { - $this->refreshToken = $token_json->refresh_token; - } - - // Success! - return true; + if ($accessToken) { + $this->accessToken = $accessToken; } - throw new OpenIDConnectClientException ('Unable to verify JWT claims'); - } - - if ($this->allowImplicitFlow && isset($_REQUEST['id_token'])) { - // if we have no code but an id_token use that - $id_token = $_REQUEST['id_token']; - - $accessToken = $_REQUEST['access_token'] ?? null; - - // Do an OpenID Connect session check - if (!isset($_REQUEST['state']) || ($_REQUEST['state'] !== $this->getState())) { - throw new OpenIDConnectClientException('Unable to determine state'); - } - - // Cleanup state - $this->unsetState(); - - $claims = $this->decodeJWT($id_token, 1); - - // Verify the signature - $this->verifySignatures($id_token); - - // Save the id token - $this->idToken = $id_token; - - // If this is a valid claim - if ($this->verifyJWTClaims($claims, $accessToken)) { - - // Clean up the session a little - $this->unsetNonce(); - - // Save the verified claims - $this->verifiedClaims = $claims; - - // Save the access token - if ($accessToken) { - $this->accessToken = $accessToken; - } - - // Success! - return true; - - } + // Success! + return true; - throw new OpenIDConnectClientException ('Unable to verify JWT claims'); } - $this->requestAuthorization(); - return false; + throw new OpenIDConnectClientException ('Unable to verify JWT claims'); } /** @@ -732,11 +751,11 @@ protected function generateRandString(): string /** * Start Here - * @return void + * @return string * @throws OpenIDConnectClientException * @throws Exception */ - private function requestAuthorization() { + public function requestAuthorization(): string { $auth_endpoint = $this->getProviderConfigValue('authorization_endpoint'); $response_type = 'code'; @@ -786,7 +805,8 @@ private function requestAuthorization() { $auth_endpoint .= (strpos($auth_endpoint, '?') === false ? '?' : '&') . http_build_query($auth_params, '', '&', $this->encType); $this->commitSession(); - $this->redirect($auth_endpoint); + + return $auth_endpoint; } /**