Skip to content
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,37 @@ $oidc->setPrivateKeyJwtGenerator(function(string $token_endpoint) {
})
```

## Example 11: Basic Client splitting up the process in individual actions

```php
// controllers/oidc_request_authorization.php
use Jumbojett\OpenIDConnectClient;

$oidc = new OpenIDConnectClient('https://id.provider.com',
'ClientIDHere',
'ClientSecretHere');
$oidc->setCertPath('/path/to/my.cert');

$auth_endpoint = $oidc->requestAuthorization();
$redirectUrl = $oidc->redirect($auth_endpoint);

$oidc->redirect($redirectUrl);
```

```php
// controllers/oidc_convert_code_into_tokens.php
use Jumbojett\OpenIDConnectClient;

$oidc = new OpenIDConnectClient('https://id.provider.com',
'ClientIDHere',
'ClientSecretHere');
$oidc->setCertPath('/path/to/my.cert');

$oidc->handleCode($_REQUEST['code']);

$idToken = $oidc->getIdToken();
$accessToken = $oidc->getAccessToken();
```

## Development Environments ##
In some cases you may need to disable SSL security on your development systems.
Expand Down
240 changes: 130 additions & 110 deletions src/OpenIDConnectClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -291,133 +291,152 @@ public function setIssuer($issuer) {
public function setResponseTypes($response_types) {
$this->responseTypes = array_merge($this->responseTypes, (array)$response_types);
}

/**
* use this method to magically handle all incoming OIDC requests
* if you need more control per request, use the methods handleError(), handleCode(), handleClaims(), requestAuthorisation() and redirect()
* @return bool
* @throws OpenIDConnectClientException
*/
public function authenticate(): bool {
// Do a preemptive check to see if the provider has thrown an error from a previous redirect
if (isset($_REQUEST['error'])) {
// always throws an exception, routine will end here
$this->handleError($_REQUEST['error'], $_REQUEST['error_description'] ?? null);
}

// If we have an authorization code then proceed to request a token
if (isset($_REQUEST['code'])) {
return $this->handleCode($_REQUEST['code'], $_REQUEST['state'] ?? null);
}

if ($this->allowImplicitFlow && isset($_REQUEST['id_token'])) {
return $this->handleClaims($_REQUEST['id_token'], $_REQUEST['access_token'] ?? null, $_REQUEST['state'] ?? null);
}

$auth_endpoint = $this->requestAuthorization();
$this->redirect($auth_endpoint);

return false;
}

/**
* @throws OpenIDConnectClientException
*/
public function handleError(string $error, string $errorDescription = null) {
$desc = $errorDescription !== null ? ' Description: '.$errorDescription : '';
throw new OpenIDConnectClientException('Error: '.$error.$desc);
}

/**
* @throws OpenIDConnectClientException
*/
public function handleCode(string $code, string $state = null): bool {
$token_json = $this->requestTokens($code);

// Throw an error if the server returns one
if (isset($token_json->error)) {
if (isset($token_json->error_description)) {
throw new OpenIDConnectClientException($token_json->error_description);
}
throw new OpenIDConnectClientException('Got response: '.$token_json->error);
}

// Do an OpenID Connect session check
if ($state === null || ($state !== $this->getState())) {
throw new OpenIDConnectClientException('Unable to determine state');
}

// Cleanup state
$this->unsetState();

if (!property_exists($token_json, 'id_token')) {
throw new OpenIDConnectClientException('User did not authorize openid scope.');
}

$id_token = $token_json->id_token;
$idTokenHeaders = $this->decodeJWT($id_token);
if (isset($idTokenHeaders->enc)) {
// Handle JWE
$id_token = $this->handleJweResponse($id_token);
}

$claims = $this->decodeJWT($id_token, 1);

// Verify the signature
$this->verifySignatures($id_token);

// Save the id token
$this->idToken = $id_token;

// Save the access token
$this->accessToken = $token_json->access_token;

// If this is a valid claim
if ($this->verifyJWTClaims($claims, $token_json->access_token)) {

// Clean up the session a little
$this->unsetNonce();

// Save the full response
$this->tokenResponse = $token_json;

// Save the verified claims
$this->verifiedClaims = $claims;

// Save the refresh token, if we got one
if (isset($token_json->refresh_token)) {
$this->refreshToken = $token_json->refresh_token;
}

// Success!
return true;
}

throw new OpenIDConnectClientException ('Unable to verify JWT claims');
}

/**
* @return bool
* @throws OpenIDConnectClientException
*/
public function authenticate(): bool
public function handleClaims(string $id_token, string $accessToken = null, string $state = null): bool
{
// Do a preemptive check to see if the provider has thrown an error from a previous redirect
if (isset($_REQUEST['error'])) {
$desc = isset($_REQUEST['error_description']) ? ' Description: ' . $_REQUEST['error_description'] : '';
throw new OpenIDConnectClientException('Error: ' . $_REQUEST['error'] .$desc);
// Do an OpenID Connect session check
if ($state === null || ($state !== $this->getState())) {
throw new OpenIDConnectClientException('Unable to determine state');
}

// If we have an authorization code then proceed to request a token
if (isset($_REQUEST['code'])) {

$code = $_REQUEST['code'];
$token_json = $this->requestTokens($code);

// Throw an error if the server returns one
if (isset($token_json->error)) {
if (isset($token_json->error_description)) {
throw new OpenIDConnectClientException($token_json->error_description);
}
throw new OpenIDConnectClientException('Got response: ' . $token_json->error);
}

// Do an OpenID Connect session check
if (!isset($_REQUEST['state']) || ($_REQUEST['state'] !== $this->getState())) {
throw new OpenIDConnectClientException('Unable to determine state');
}
// Cleanup state
$this->unsetState();

// Cleanup state
$this->unsetState();
$claims = $this->decodeJWT($id_token, 1);

if (!property_exists($token_json, 'id_token')) {
throw new OpenIDConnectClientException('User did not authorize openid scope.');
}
// Verify the signature
$this->verifySignatures($id_token);

$id_token = $token_json->id_token;
$idTokenHeaders = $this->decodeJWT($id_token);
if (isset($idTokenHeaders->enc)) {
// Handle JWE
$id_token = $this->handleJweResponse($id_token);
}
// Save the id token
$this->idToken = $id_token;

$claims = $this->decodeJWT($id_token, 1);
// If this is a valid claim
if ($this->verifyJWTClaims($claims, $accessToken)) {

// Verify the signature
$this->verifySignatures($id_token);
// Clean up the session a little
$this->unsetNonce();

// Save the id token
$this->idToken = $id_token;
// Save the verified claims
$this->verifiedClaims = $claims;

// Save the access token
$this->accessToken = $token_json->access_token;

// If this is a valid claim
if ($this->verifyJWTClaims($claims, $token_json->access_token)) {

// Clean up the session a little
$this->unsetNonce();

// Save the full response
$this->tokenResponse = $token_json;

// Save the verified claims
$this->verifiedClaims = $claims;

// Save the refresh token, if we got one
if (isset($token_json->refresh_token)) {
$this->refreshToken = $token_json->refresh_token;
}

// Success!
return true;
if ($accessToken) {
$this->accessToken = $accessToken;
}

throw new OpenIDConnectClientException ('Unable to verify JWT claims');
}

if ($this->allowImplicitFlow && isset($_REQUEST['id_token'])) {
// if we have no code but an id_token use that
$id_token = $_REQUEST['id_token'];

$accessToken = $_REQUEST['access_token'] ?? null;

// Do an OpenID Connect session check
if (!isset($_REQUEST['state']) || ($_REQUEST['state'] !== $this->getState())) {
throw new OpenIDConnectClientException('Unable to determine state');
}

// Cleanup state
$this->unsetState();

$claims = $this->decodeJWT($id_token, 1);

// Verify the signature
$this->verifySignatures($id_token);

// Save the id token
$this->idToken = $id_token;

// If this is a valid claim
if ($this->verifyJWTClaims($claims, $accessToken)) {

// Clean up the session a little
$this->unsetNonce();

// Save the verified claims
$this->verifiedClaims = $claims;

// Save the access token
if ($accessToken) {
$this->accessToken = $accessToken;
}

// Success!
return true;

}
// Success!
return true;

throw new OpenIDConnectClientException ('Unable to verify JWT claims');
}

$this->requestAuthorization();
return false;
throw new OpenIDConnectClientException ('Unable to verify JWT claims');
}

/**
Expand Down Expand Up @@ -732,11 +751,11 @@ protected function generateRandString(): string

/**
* Start Here
* @return void
* @return string
* @throws OpenIDConnectClientException
* @throws Exception
*/
private function requestAuthorization() {
public function requestAuthorization(): string {

$auth_endpoint = $this->getProviderConfigValue('authorization_endpoint');
$response_type = 'code';
Expand Down Expand Up @@ -786,7 +805,8 @@ private function requestAuthorization() {
$auth_endpoint .= (strpos($auth_endpoint, '?') === false ? '?' : '&') . http_build_query($auth_params, '', '&', $this->encType);

$this->commitSession();
$this->redirect($auth_endpoint);

return $auth_endpoint;
}

/**
Expand Down
4 changes: 2 additions & 2 deletions tests/OpenIDConnectClientTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,8 @@ public function testAuthenticateDoesNotThrowExceptionIfClaimsIsMissingNonce()
$fakeClaims->nonce = null;

$_REQUEST['id_token'] = 'abc.123.xyz';
$_REQUEST['state'] = false;
$_SESSION['openid_connect_state'] = false;
$_REQUEST['state'] = 'false';
$_SESSION['openid_connect_state'] = 'false';

/** @var OpenIDConnectClient | MockObject $client */
$client = $this->getMockBuilder(OpenIDConnectClient::class)->setMethods(['decodeJWT', 'getProviderConfigValue', 'verifyJWTSignature'])->getMock();
Expand Down