@@ -226,6 +226,17 @@ class OpenIDConnectClient
226226
227227 protected $ enc_type = PHP_QUERY_RFC1738 ;
228228
229+ /**
230+ * @var string holds code challenge method for PKCE mode
231+ * @see https://tools.ietf.org/html/rfc7636
232+ */
233+ private $ codeChallengeMethod = false ;
234+
235+ /**
236+ * @var array holds PKCE supported algorithms
237+ */
238+ private $ pkceAlgs = array ('S256 ' => 'sha256 ' , 'plain ' => false );
239+
229240 /**
230241 * @param $provider_url string optional
231242 *
@@ -649,6 +660,21 @@ private function requestAuthorization() {
649660 $ auth_params = array_merge ($ auth_params , array ('response_type ' => implode (' ' , $ this ->responseTypes )));
650661 }
651662
663+ // If the client supports Proof Key for Code Exchange (PKCE)
664+ if (!empty ($ this ->getCodeChallengeMethod ()) && in_array ($ this ->getCodeChallengeMethod (), $ this ->getProviderConfigValue ('code_challenge_methods_supported ' ))) {
665+ $ codeVerifier = bin2hex (random_bytes (64 ));
666+ $ this ->setCodeVerifier ($ codeVerifier );
667+ if (!empty ($ this ->pkceAlgs [$ this ->getCodeChallengeMethod ()])) {
668+ $ codeChallenge = rtrim (strtr (base64_encode (hash ($ this ->pkceAlgs [$ this ->getCodeChallengeMethod ()], $ codeVerifier , true )), '+/ ' , '-_ ' ), '= ' );
669+ } else {
670+ $ codeChallenge = $ codeVerifier ;
671+ }
672+ $ auth_params = array_merge ($ auth_params , array (
673+ 'code_challenge ' => $ codeChallenge ,
674+ 'code_challenge_method ' => $ this ->getCodeChallengeMethod ()
675+ ));
676+ }
677+
652678 $ auth_endpoint .= (strpos ($ auth_endpoint , '? ' ) === false ? '? ' : '& ' ) . http_build_query ($ auth_params , null , '& ' , $ this ->enc_type );
653679
654680 $ this ->commitSession ();
@@ -746,6 +772,15 @@ protected function requestTokens($code) {
746772 unset($ token_params ['client_id ' ]);
747773 }
748774
775+ if (!empty ($ this ->getCodeChallengeMethod ()) && !empty ($ this ->getCodeVerifier ())) {
776+ $ headers = [];
777+ unset($ token_params ['client_secret ' ]);
778+ $ token_params = array_merge ($ token_params , array (
779+ 'client_id ' => $ this ->clientID ,
780+ 'code_verifier ' => $ this ->getCodeVerifier ()
781+ ));
782+ }
783+
749784 // Convert token params to string format
750785 $ token_params = http_build_query ($ token_params , null , '& ' , $ this ->enc_type );
751786
@@ -1588,6 +1623,35 @@ protected function unsetState() {
15881623 $ this ->unsetSessionKey ('openid_connect_state ' );
15891624 }
15901625
1626+ /**
1627+ * Stores $codeVerifier
1628+ *
1629+ * @param string $codeVerifier
1630+ * @return string
1631+ */
1632+ protected function setCodeVerifier ($ codeVerifier ) {
1633+ $ this ->setSessionKey ('openid_connect_code_verifier ' , $ codeVerifier );
1634+ return $ codeVerifier ;
1635+ }
1636+
1637+ /**
1638+ * Get stored codeVerifier
1639+ *
1640+ * @return string
1641+ */
1642+ protected function getCodeVerifier () {
1643+ return $ this ->getSessionKey ('openid_connect_code_verifier ' );
1644+ }
1645+
1646+ /**
1647+ * Cleanup state
1648+ *
1649+ * @return void
1650+ */
1651+ protected function unsetCodeVerifier () {
1652+ $ this ->unsetSessionKey ('openid_connect_code_verifier ' );
1653+ }
1654+
15911655 /**
15921656 * Get the response code from last action/curl request.
15931657 *
@@ -1741,4 +1805,18 @@ public function getLeeway()
17411805 {
17421806 return $ this ->leeway ;
17431807 }
1808+
1809+ /**
1810+ * @return string
1811+ */
1812+ public function getCodeChallengeMethod () {
1813+ return $ this ->codeChallengeMethod ;
1814+ }
1815+
1816+ /**
1817+ * @param string $codeChallengeMethod
1818+ */
1819+ public function setCodeChallengeMethod ($ codeChallengeMethod ) {
1820+ $ this ->codeChallengeMethod = $ codeChallengeMethod ;
1821+ }
17441822}
0 commit comments