From ce972309057b3cf1967ba559346e9d6d3d667503 Mon Sep 17 00:00:00 2001 From: nikosev Date: Thu, 29 Oct 2020 14:22:46 +0200 Subject: [PATCH] Add support for PKCE --- CHANGELOG.md | 5 +++ README.md | 14 +++++++ src/OpenIDConnectClient.php | 78 +++++++++++++++++++++++++++++++++++++ 3 files changed, 97 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 23e3d8d0..7d6eca88 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,11 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). +## [Unreleased] + +### Added +* Support for [PKCE](https://tools.ietf.org/html/rfc7636). Currentlly the supported methods are 'plain' and 'S256'. + ## [0.9.1] ### Added diff --git a/README.md b/README.md index 8ea18a9d..7044f216 100644 --- a/README.md +++ b/README.md @@ -129,6 +129,20 @@ if (!$data->active) { ``` +## Example 8: PKCE Client ## + +```php +use Jumbojett\OpenIDConnectClient; + +$oidc = new OpenIDConnectClient('https://id.provider.com', + 'ClientIDHere', + null); +$oidc->setCodeChallengeMethod('S256'); +$oidc->authenticate(); +$name = $oidc->requestUserInfo('given_name'); + +``` + ## Development Environments ## In some cases you may need to disable SSL security on on your development systems. diff --git a/src/OpenIDConnectClient.php b/src/OpenIDConnectClient.php index f13f6eaf..2b3f11e1 100644 --- a/src/OpenIDConnectClient.php +++ b/src/OpenIDConnectClient.php @@ -226,6 +226,17 @@ class OpenIDConnectClient protected $enc_type = PHP_QUERY_RFC1738; + /** + * @var string holds code challenge method for PKCE mode + * @see https://tools.ietf.org/html/rfc7636 + */ + private $codeChallengeMethod = false; + + /** + * @var array holds PKCE supported algorithms + */ + private $pkceAlgs = array('S256' => 'sha256', 'plain' => false); + /** * @param $provider_url string optional * @@ -640,6 +651,21 @@ private function requestAuthorization() { $auth_params = array_merge($auth_params, array('response_type' => implode(' ', $this->responseTypes))); } + // If the client supports Proof Key for Code Exchange (PKCE) + if (!empty($this->getCodeChallengeMethod()) && in_array($this->getCodeChallengeMethod(), $this->getProviderConfigValue('code_challenge_methods_supported'))) { + $codeVerifier = bin2hex(random_bytes(64)); + $this->setCodeVerifier($codeVerifier); + if (!empty($this->pkceAlgs[$this->getCodeChallengeMethod()])) { + $codeChallenge = rtrim(strtr(base64_encode(hash($this->pkceAlgs[$this->getCodeChallengeMethod()], $codeVerifier, true)), '+/', '-_'), '='); + } else { + $codeChallenge = $codeVerifier; + } + $auth_params = array_merge($auth_params, array( + 'code_challenge' => $codeChallenge, + 'code_challenge_method' => $this->getCodeChallengeMethod() + )); + } + $auth_endpoint .= (strpos($auth_endpoint, '?') === false ? '?' : '&') . http_build_query($auth_params, null, '&', $this->enc_type); $this->commitSession(); @@ -737,6 +763,15 @@ protected function requestTokens($code) { unset($token_params['client_id']); } + if (!empty($this->getCodeVerifier())) { + $headers = []; + unset($token_params['client_secret']); + $token_params = array_merge($token_params, array( + 'client_id' => $this->clientID, + 'code_verifier' => $this->getCodeVerifier() + )); + } + // Convert token params to string format $token_params = http_build_query($token_params, null, '&', $this->enc_type); @@ -1579,6 +1614,35 @@ protected function unsetState() { $this->unsetSessionKey('openid_connect_state'); } + /** + * Stores $codeVerifier + * + * @param string $codeVerifier + * @return string + */ + protected function setCodeVerifier($codeVerifier) { + $this->setSessionKey('openid_connect_code_verifier', $codeVerifier); + return $codeVerifier; + } + + /** + * Get stored codeVerifier + * + * @return string + */ + protected function getCodeVerifier() { + return $this->getSessionKey('openid_connect_code_verifier'); + } + + /** + * Cleanup state + * + * @return void + */ + protected function unsetCodeVerifier() { + $this->unsetSessionKey('openid_connect_code_verifier'); + } + /** * Get the response code from last action/curl request. * @@ -1732,4 +1796,18 @@ public function getLeeway() { return $this->leeway; } + + /** + * @return string + */ + public function getCodeChallengeMethod() { + return $this->codeChallengeMethod; + } + + /** + * @param string $codeChallengeMethod + */ + public function setCodeChallengeMethod($codeChallengeMethod) { + $this->codeChallengeMethod = $codeChallengeMethod; + } }