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}