|
@@ -17,6 +17,7 @@ namespace League\OAuth2\Client\Provider;
|
|
|
use GuzzleHttp\Client as HttpClient;
|
|
|
use GuzzleHttp\ClientInterface as HttpClientInterface;
|
|
|
use GuzzleHttp\Exception\BadResponseException;
|
|
|
+use InvalidArgumentException;
|
|
|
use League\OAuth2\Client\Grant\AbstractGrant;
|
|
|
use League\OAuth2\Client\Grant\GrantFactory;
|
|
|
use League\OAuth2\Client\OptionProvider\OptionProviderInterface;
|
|
@@ -44,7 +45,7 @@ abstract class AbstractProvider
|
|
|
use QueryBuilderTrait;
|
|
|
|
|
|
/**
|
|
|
- * @var string Key used in a token response to identify the resource owner.
|
|
|
+ * @var string|null Key used in a token response to identify the resource owner.
|
|
|
*/
|
|
|
const ACCESS_TOKEN_RESOURCE_OWNER_ID = null;
|
|
|
|
|
@@ -58,6 +59,19 @@ abstract class AbstractProvider
|
|
|
*/
|
|
|
const METHOD_POST = 'POST';
|
|
|
|
|
|
+ /**
|
|
|
+ * @var string PKCE method used to fetch authorization token.
|
|
|
+ * The PKCE code challenge will be hashed with sha256 (recommended).
|
|
|
+ */
|
|
|
+ const PKCE_METHOD_S256 = 'S256';
|
|
|
+
|
|
|
+ /**
|
|
|
+ * @var string PKCE method used to fetch authorization token.
|
|
|
+ * The PKCE code challenge will be sent as plain text, this is NOT recommended.
|
|
|
+ * Only use `plain` if no other option is possible.
|
|
|
+ */
|
|
|
+ const PKCE_METHOD_PLAIN = 'plain';
|
|
|
+
|
|
|
/**
|
|
|
* @var string
|
|
|
*/
|
|
@@ -78,6 +92,11 @@ abstract class AbstractProvider
|
|
|
*/
|
|
|
protected $state;
|
|
|
|
|
|
+ /**
|
|
|
+ * @var string|null
|
|
|
+ */
|
|
|
+ protected $pkceCode = null;
|
|
|
+
|
|
|
/**
|
|
|
* @var GrantFactory
|
|
|
*/
|
|
@@ -264,6 +283,32 @@ abstract class AbstractProvider
|
|
|
return $this->state;
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * Set the value of the pkceCode parameter.
|
|
|
+ *
|
|
|
+ * When using PKCE this should be set before requesting an access token.
|
|
|
+ *
|
|
|
+ * @param string $pkceCode
|
|
|
+ * @return self
|
|
|
+ */
|
|
|
+ public function setPkceCode($pkceCode)
|
|
|
+ {
|
|
|
+ $this->pkceCode = $pkceCode;
|
|
|
+ return $this;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Returns the current value of the pkceCode parameter.
|
|
|
+ *
|
|
|
+ * This can be accessed by the redirect handler during authorization.
|
|
|
+ *
|
|
|
+ * @return string|null
|
|
|
+ */
|
|
|
+ public function getPkceCode()
|
|
|
+ {
|
|
|
+ return $this->pkceCode;
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
* Returns the base URL for authorizing a client.
|
|
|
*
|
|
@@ -305,6 +350,27 @@ abstract class AbstractProvider
|
|
|
return bin2hex(random_bytes($length / 2));
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * Returns a new random string to use as PKCE code_verifier and
|
|
|
+ * hashed as code_challenge parameters in an authorization flow.
|
|
|
+ * Must be between 43 and 128 characters long.
|
|
|
+ *
|
|
|
+ * @param int $length Length of the random string to be generated.
|
|
|
+ * @return string
|
|
|
+ */
|
|
|
+ protected function getRandomPkceCode($length = 64)
|
|
|
+ {
|
|
|
+ return substr(
|
|
|
+ strtr(
|
|
|
+ base64_encode(random_bytes($length)),
|
|
|
+ '+/',
|
|
|
+ '-_'
|
|
|
+ ),
|
|
|
+ 0,
|
|
|
+ $length
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
* Returns the default scopes used by this provider.
|
|
|
*
|
|
@@ -326,6 +392,14 @@ abstract class AbstractProvider
|
|
|
return ',';
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * @return string|null
|
|
|
+ */
|
|
|
+ protected function getPkceMethod()
|
|
|
+ {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
* Returns authorization parameters based on provided options.
|
|
|
*
|
|
@@ -355,6 +429,26 @@ abstract class AbstractProvider
|
|
|
// Store the state as it may need to be accessed later on.
|
|
|
$this->state = $options['state'];
|
|
|
|
|
|
+ $pkceMethod = $this->getPkceMethod();
|
|
|
+ if (!empty($pkceMethod)) {
|
|
|
+ $this->pkceCode = $this->getRandomPkceCode();
|
|
|
+ if ($pkceMethod === static::PKCE_METHOD_S256) {
|
|
|
+ $options['code_challenge'] = trim(
|
|
|
+ strtr(
|
|
|
+ base64_encode(hash('sha256', $this->pkceCode, true)),
|
|
|
+ '+/',
|
|
|
+ '-_'
|
|
|
+ ),
|
|
|
+ '='
|
|
|
+ );
|
|
|
+ } elseif ($pkceMethod === static::PKCE_METHOD_PLAIN) {
|
|
|
+ $options['code_challenge'] = $this->pkceCode;
|
|
|
+ } else {
|
|
|
+ throw new InvalidArgumentException('Unknown PKCE method "' . $pkceMethod . '".');
|
|
|
+ }
|
|
|
+ $options['code_challenge_method'] = $pkceMethod;
|
|
|
+ }
|
|
|
+
|
|
|
// Business code layer might set a different redirect_uri parameter
|
|
|
// depending on the context, leave it as-is
|
|
|
if (!isset($options['redirect_uri'])) {
|
|
@@ -517,8 +611,8 @@ abstract class AbstractProvider
|
|
|
/**
|
|
|
* Requests an access token using a specified grant and option set.
|
|
|
*
|
|
|
- * @param mixed $grant
|
|
|
- * @param array $options
|
|
|
+ * @param mixed $grant
|
|
|
+ * @param array<string, mixed> $options
|
|
|
* @throws IdentityProviderException
|
|
|
* @return AccessTokenInterface
|
|
|
*/
|
|
@@ -532,6 +626,10 @@ abstract class AbstractProvider
|
|
|
'redirect_uri' => $this->redirectUri,
|
|
|
];
|
|
|
|
|
|
+ if (!empty($this->pkceCode)) {
|
|
|
+ $params['code_verifier'] = $this->pkceCode;
|
|
|
+ }
|
|
|
+
|
|
|
$params = $grant->prepareRequestParameters($params, $options);
|
|
|
$request = $this->getAccessTokenRequest($params);
|
|
|
$response = $this->getParsedResponse($request);
|
|
@@ -564,7 +662,7 @@ abstract class AbstractProvider
|
|
|
*
|
|
|
* @param string $method
|
|
|
* @param string $url
|
|
|
- * @param AccessTokenInterface|string $token
|
|
|
+ * @param AccessTokenInterface|string|null $token
|
|
|
* @param array $options Any of "headers", "body", and "protocolVersion".
|
|
|
* @return RequestInterface
|
|
|
*/
|