/**
* Checks a provider response for errors.
- *
- * @param ResponseInterface $response
- * @param array|string $data Parsed response data
- *
* @throws IdentityProviderException
- *
- * @return void
*/
- protected function checkResponse(ResponseInterface $response, $data)
+ protected function checkResponse(ResponseInterface $response, $data): void
{
if ($response->getStatusCode() >= 400 || isset($data['error'])) {
throw new IdentityProviderException(
/**
* Generates a resource owner object from a successful resource owner
* details request.
- *
- * @param array $response
- * @param AccessToken $token
- *
- * @return ResourceOwnerInterface
*/
- protected function createResourceOwner(array $response, AccessToken $token)
+ protected function createResourceOwner(array $response, AccessToken $token): ResourceOwnerInterface
{
return new GenericResourceOwner($response, '');
}
*
* The grant that was used to fetch the response can be used to provide
* additional context.
- *
- * @param array $response
- * @param AbstractGrant $grant
- *
- * @return OidcAccessToken
*/
- protected function createAccessToken(array $response, AbstractGrant $grant)
+ protected function createAccessToken(array $response, AbstractGrant $grant): OidcAccessToken
{
return new OidcAccessToken($response);
}
+
+ /**
+ * Get the method used for PKCE code verifier hashing, which is passed
+ * in the "code_challenge_method" parameter in the authorization request.
+ */
+ protected function getPkceMethod(): string
+ {
+ return static::PKCE_METHOD_S256;
+ }
}
/**
* Initiate an authorization flow.
+ * Provides back an authorize redirect URL, in addition to other
+ * details which may be required for the auth flow.
*
* @throws OidcException
*
{
$settings = $this->getProviderSettings();
$provider = $this->getProvider($settings);
+
+ $url = $provider->getAuthorizationUrl();
+ session()->put('oidc_pkce_code', $provider->getPkceCode() ?? '');
+
return [
- 'url' => $provider->getAuthorizationUrl(),
+ 'url' => $url,
'state' => $provider->getState(),
];
}
$settings = $this->getProviderSettings();
$provider = $this->getProvider($settings);
+ // Set PKCE code flashed at login
+ $pkceCode = session()->pull('oidc_pkce_code', '');
+ $provider->setPkceCode($pkceCode);
+
// Try to exchange authorization code for access token
$accessToken = $provider->getAccessToken('authorization_code', [
'code' => $authorizationCode,
]);
}
+ public function test_pkce_used_on_authorize_and_access()
+ {
+ // Start auth
+ $resp = $this->post('/oidc/login');
+ $state = session()->get('oidc_state');
+
+ $pkceCode = session()->get('oidc_pkce_code');
+ $this->assertGreaterThan(30, strlen($pkceCode));
+
+ $expectedCodeChallenge = trim(strtr(base64_encode(hash('sha256', $pkceCode, true)), '+/', '-_'), '=');
+ $redirect = $resp->headers->get('Location');
+ $redirectParams = [];
+ parse_str(parse_url($redirect, PHP_URL_QUERY), $redirectParams);
+ $this->assertEquals($expectedCodeChallenge, $redirectParams['code_challenge']);
+ $this->assertEquals('S256', $redirectParams['code_challenge_method']);
+
+ $transactions = $this->mockHttpClient([$this->getMockAuthorizationResponse([
+ 'sub' => 'benny1010101',
+ ])]);
+
+ $this->get('/oidc/callback?code=SplxlOBeZQQYbYS6WxSbIA&state=' . $state);
+ $tokenRequest = $transactions->latestRequest();
+ $bodyParams = [];
+ parse_str($tokenRequest->getBody(), $bodyParams);
+ $this->assertEquals($pkceCode, $bodyParams['code_verifier']);
+ }
+
protected function withAutodiscovery()
{
config()->set([