Skip to content

Commit

Permalink
Work on #16
Browse files Browse the repository at this point in the history
- Added validateAccessToken function
- Updated documentation to reflect validateAccessToken function
  • Loading branch information
hajekj committed Apr 17, 2016
1 parent afdb11e commit 712ba21
Show file tree
Hide file tree
Showing 3 changed files with 107 additions and 48 deletions.
23 changes: 22 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ This package provides [Azure Active Directory](https://azure.microsoft.com/en-us
- [Variables](#variables)
- [Resource Owner](#resource-owner)
- [Microsoft Graph](#microsoft-graph)
- [**NEW** - Protecting your API - *experimental*](#protecting-your-api---experimental)
- [Azure Active Directory B2C - *experimental*](#azure-active-directory-b2c---experimental)
- [Multipurpose refresh tokens - *experimental*](#multipurpose-refresh-tokens---experimental)
- [Known users](#known-users)
Expand Down Expand Up @@ -144,14 +145,34 @@ $provider->urlAPI = "https://graph.microsoft.com/v1.0/";
```
After that, when requesting access token, refresh token or so, provide the `resource` with value `https://graph.microsoft.com/` in order to be able to make calls to the Graph (see more about `resource` [here](#advanced-flow)).

## Protecting your API - *experimental*
With version 1.2.0 you can now use this library to protect your API with Azure Active Directory authentication very easily. The Provider now also exposes `validateAccessToken(string $token)` which lets you pass an access token inside which you for example received in the `Authorization` header of the request on your API. You can use the function followingly (in vanilla PHP):
```php
// Assuming you have already initialized the $provider

// Obtain the accessToken - in this case, we are getting it from Authorization header
$headers = getallheaders();
$authorization = explode(' ', $headers['Authorization']);
$accessToken = $authorization[1];

try {
$claims = $provider->validateAccessToken($accessToken);
} catch ($e) {
// Something happened, handle the error
}

// The access token is valid, you can now proceed with your code. You can also access the $claims as defined in JWT - for example roles, group memberships etc.
```
While doesn't doesn't allow you to request tokens for additional resources (`on_behalf_of`), I believe it is a good start. The support for such grant is going to be available in later release.

## Azure Active Directory B2C - *experimental*
You can also now very simply make use of [Azure Active Directory B2C](https://azure.microsoft.com/en-us/documentation/articles/active-directory-b2c-reference-oauth-code/). Before authentication, change the endpoints using `pathAuthorize`, `pathToken` and `scope` and additionally specify your [login policy](https://azure.microsoft.com/en-gb/documentation/articles/active-directory-b2c-reference-policies/). **Please note that the B2C support is still experimental and wasn't fully tested.**
```php
$provider->pathAuthorize = "/oauth2/v2.0/authorize";
$provider->pathToken = "/oauth2/v2.0/token";
$provider->scope = ["idtoken"];

//specify custom policy in our authorization URL
// Specify custom policy in our authorization URL
$authUrl = $provider->getAuthorizationUrl([
'p' => 'b2c_1_siup'
]);
Expand Down
82 changes: 82 additions & 0 deletions src/Provider/Azure.php
Original file line number Diff line number Diff line change
Expand Up @@ -188,4 +188,86 @@ public function getClientId()
{
return $this->clientId;
}

/**
* Validate the access token you received in your application.
*
* @input $accessToken string The access token you received in the authorization header.
*
* @return array
*/
public function validateAccessToken(string $accessToken)
{
$keys = $this->getJwtVerificationKeys();
$tokenClaims = (array)JWT::decode($accessToken, $keys, ['RS256']);

if($provider->getClientId() != $tokenClaims['aud']) {
throw new RuntimeException("The audience is invalid!");
}
if($tokenClaims['nbf'] > time() || $tokenClaims['exp'] < time()) {
// Additional validation is being performed in firebase/JWT itself
throw new RuntimeException("The id_token is invalid!");
}

if($provider->tenant == "common") {
$provider->tenant = $tokenClaims['tid'];

$tenant = $provider->getTenantDetails($provider->tenant);
if($tokenClaims['iss'] != $tenant['issuer']) {
throw new RuntimeException("Invalid token issuer!");
}
}
else {
$tenant = $provider->getTenantDetails($provider->tenant);
if($tokenClaims['iss'] != $tenant['issuer']) {
throw new RuntimeException("Invalid token issuer!");
}
}

return $tokenClaims;
}

/**
* Get JWT verification keys from Azure Active Directory.
*
* @return array
*/
public function getJwtVerificationKeys()
{
$factory = $this->getRequestFactory();
$request = $factory->getRequestWithOptions('get', 'https://login.windows.net/common/discovery/keys', []);

$response = $this->getResponse($request);

$keys = [];
foreach ($response['keys'] as $i => $keyinfo) {
if (isset($keyinfo['x5c']) && is_array($keyinfo['x5c'])) {
foreach ($keyinfo['x5c'] as $encodedkey) {
$key = "-----BEGIN CERTIFICATE-----\n";
$key .= wordwrap($encodedkey, 64, "\n", true);
$key .= "\n-----END CERTIFICATE-----";
$keys[$keyinfo['kid']] = $key;
}
}
}

return $keys;
}

/**
* Get the specified tenant's details.
*
* @param string $tenant
*
* @return array
*/
private function getTenantDetails($tenant)
{
$factory = $this->getRequestFactory();
$request = $factory->getRequestWithOptions('get', 'https://login.windows.net/'.$tenant.'/.well-known/openid-configuration', []);

$response = $this->getResponse($request);

return $response;
}
}
50 changes: 3 additions & 47 deletions src/Token/AccessToken.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public function __construct(array $options = [], $provider)
if (!empty($options['id_token'])) {
$this->idToken = $options['id_token'];

$keys = $this->getJwtVerificationKeys($provider);
$keys = $provider->getJwtVerificationKeys();
$idTokenClaims = null;
try {
$tks = explode('.', $this->idToken);
Expand Down Expand Up @@ -47,13 +47,13 @@ public function __construct(array $options = [], $provider)
if($provider->tenant == "common") {
$provider->tenant = $idTokenClaims['tid'];

$tenant = $this->getTenantDetails($provider->tenant, $provider);
$tenant = $provider->getTenantDetails($provider->tenant);
if($idTokenClaims['iss'] != $tenant['issuer']) {
throw new RuntimeException("Invalid token issuer!");
}
}
else {
$tenant = $this->getTenantDetails($provider->tenant, $provider);
$tenant = $provider->getTenantDetails($provider->tenant);
if($idTokenClaims['iss'] != $tenant['issuer']) {
throw new RuntimeException("Invalid token issuer!");
}
Expand All @@ -63,50 +63,6 @@ public function __construct(array $options = [], $provider)
}
}

/**
* Get JWT verification keys from Azure Active Directory.
*
* @return array
*/
private function getJwtVerificationKeys($provider)
{
$factory = $provider->getRequestFactory();
$request = $factory->getRequestWithOptions('get', 'https://login.windows.net/common/discovery/keys', []);

$response = $provider->getResponse($request);

$keys = [];
foreach ($response['keys'] as $i => $keyinfo) {
if (isset($keyinfo['x5c']) && is_array($keyinfo['x5c'])) {
foreach ($keyinfo['x5c'] as $encodedkey) {
$key = "-----BEGIN CERTIFICATE-----\n";
$key .= wordwrap($encodedkey, 64, "\n", true);
$key .= "\n-----END CERTIFICATE-----";
$keys[$keyinfo['kid']] = $key;
}
}
}

return $keys;
}

/**
* Get the specified tenant's details.
*
* @param string $tenant
*
* @return array
*/
private function getTenantDetails($tenant, $provider)
{
$factory = $provider->getRequestFactory();
$request = $factory->getRequestWithOptions('get', 'https://login.windows.net/'.$tenant.'/.well-known/openid-configuration', []);

$response = $provider->getResponse($request);

return $response;
}

public function getIdTokenClaims()
{
return $this->idTokenClaims;
Expand Down

0 comments on commit 712ba21

Please sign in to comment.