friendship ended with social-app. php is my new best friend
1<?php 2/** 3 * Trait PKCETrait 4 * 5 * @created 19.09.2024 6 * @author smiley <smiley@chillerlan.net> 7 * @copyright 2024 smiley 8 * @license MIT 9 */ 10declare(strict_types=1); 11 12namespace chillerlan\OAuth\Core; 13 14use chillerlan\OAuth\Providers\ProviderException; 15use chillerlan\Utilities\{Crypto, Str}; 16use const SODIUM_BASE64_VARIANT_URLSAFE_NO_PADDING; 17 18/** 19 * Implements PKCE (Proof Key for Code Exchange) functionality 20 * 21 * @see \chillerlan\OAuth\Core\PKCE 22 */ 23trait PKCETrait{ 24 25 /** 26 * implements PKCE::setCodeChallenge() 27 * 28 * @see \chillerlan\OAuth\Core\PKCE::setCodeChallenge() 29 * @see \chillerlan\OAuth\Core\OAuth2Provider::getAuthorizationURLRequestParams() 30 * 31 * @param array<string, string> $params 32 * @return array<string, string> 33 */ 34 final public function setCodeChallenge(array $params, string $challengeMethod):array{ 35 36 if(!isset($params['response_type']) || $params['response_type'] !== 'code'){ 37 throw new ProviderException('invalid authorization request params'); 38 } 39 40 $verifier = $this->generateVerifier($this->options->pkceVerifierLength); 41 42 $params['code_challenge'] = $this->generateChallenge($verifier, $challengeMethod); 43 $params['code_challenge_method'] = $challengeMethod; 44 45 $this->storage->storeCodeVerifier($verifier, $this->name); 46 47 return $params; 48 } 49 50 /** 51 * implements PKCE::setCodeVerifier() 52 * 53 * @see \chillerlan\OAuth\Core\PKCE::setCodeVerifier() 54 * @see \chillerlan\OAuth\Core\OAuth2Provider::getAccessTokenRequestBodyParams() 55 * 56 * @param array<string, string> $params 57 * @return array<string, string> 58 */ 59 final public function setCodeVerifier(array $params):array{ 60 61 if(!isset($params['grant_type'], $params['code']) || $params['grant_type'] !== 'authorization_code'){ 62 throw new ProviderException('invalid authorization request body'); 63 } 64 65 $params['code_verifier'] = $this->storage->getCodeVerifier($this->name); 66 67 // delete verifier after use 68 $this->storage->clearCodeVerifier($this->name); 69 70 return $params; 71 } 72 73 /** 74 * implements PKCE::generateVerifier() 75 * 76 * @see \chillerlan\OAuth\Core\PKCE::generateVerifier() 77 * @see \chillerlan\OAuth\Core\OAuth2Provider::setCodeChallenge() 78 */ 79 final public function generateVerifier(int $length):string{ 80 return Crypto::randomString($length, PKCE::VERIFIER_CHARSET); 81 } 82 83 /** 84 * implements PKCE::generateChallenge() 85 * 86 * @see \chillerlan\OAuth\Core\PKCE::generateChallenge() 87 * @see \chillerlan\OAuth\Core\OAuth2Provider::setCodeChallenge() 88 */ 89 final public function generateChallenge(string $verifier, string $challengeMethod):string{ 90 91 if($challengeMethod === PKCE::CHALLENGE_METHOD_PLAIN){ 92 return $verifier; 93 } 94 95 $verifier = match($challengeMethod){ 96 PKCE::CHALLENGE_METHOD_S256 => Crypto::sha256($verifier, true), 97 // no other hash methods yet 98 default => throw new ProviderException('invalid PKCE challenge method'), // @codeCoverageIgnore 99 }; 100 101 return Str::base64encode($verifier, SODIUM_BASE64_VARIANT_URLSAFE_NO_PADDING); 102 } 103 104}