Skip to content

Commit

Permalink
Add support for using a certificate instead of a shared secret (imple…
Browse files Browse the repository at this point in the history
…ments #115).
  • Loading branch information
uncaught committed Sep 9, 2022
1 parent 06fb2d6 commit 3786c93
Show file tree
Hide file tree
Showing 2 changed files with 56 additions and 0 deletions.
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@ $provider = new TheNetworg\OAuth2\Client\Provider\Azure([
'clientId' => '{azure-client-id}',
'clientSecret' => '{azure-client-secret}',
'redirectUri' => 'https://example.com/callback-url',
//Optional using key pair instead of secret
'clientCertificatePrivateKey' => '{azure-client-certificate-private-key}',
//Optional using key pair instead of secret
'clientCertificateThumbprint' => '{azure-client-certificate-thumbprint}',
//Optional
'scopes' => ['openid'],
//Optional
Expand Down Expand Up @@ -128,6 +132,19 @@ $authUrl = $provider->getAuthorizationUrl([
```
You can find additional parameters [here](https://msdn.microsoft.com/en-us/library/azure/dn645542.aspx).

#### Using a certificate key pair instead of the shared secret

- Generate a key pair, e.g. with:
```bash
openssl genrsa -out private.key 2048
openssl req -new -x509 -key private.key -out publickey.cer -days 365
```
- Upload the `publickey.cer` to your app in the Azure portal
- Note the displayed thumbprint for the certificate (it looks like `B4A94A83092455AC4D3AC827F02B61646EAAC43D`)
- Put that thumbprint into the `clientCertificateThumbprint` constructor option
- Put the contents of `private.key` into the `clientCertificatePrivateKey` constructor option
- You can omit the `clientSecret` constructor option

### Logging out
If you need to quickly generate a logout URL for the user, you can do following:
```php
Expand Down
39 changes: 39 additions & 0 deletions src/Provider/Azure.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
use League\OAuth2\Client\Provider\ResourceOwnerInterface;
use League\OAuth2\Client\Token\AccessTokenInterface;
use League\OAuth2\Client\Tool\BearerAuthorizationTrait;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use TheNetworg\OAuth2\Client\Grant\JwtBearer;
use TheNetworg\OAuth2\Client\Token\AccessToken;
Expand Down Expand Up @@ -44,6 +45,18 @@ class Azure extends AbstractProvider

public $authWithResource = true;

/**
* The contents of the private key used for app authentication
* @var string
*/
protected $clientCertificatePrivateKey = '';

/**
* The hexadecimal certificate thumbprint as displayed in the azure portal
* @var string
*/
protected $clientCertificateThumbprint = '';

public function __construct(array $options = [], array $collaborators = [])
{
parent::__construct($options, $collaborators);
Expand Down Expand Up @@ -103,6 +116,32 @@ public function getBaseAccessTokenUrl(array $params): string
return $openIdConfiguration['token_endpoint'];
}

protected function getAccessTokenRequest(array $params): RequestInterface
{
if ($this->clientCertificatePrivateKey && $this->clientCertificateThumbprint) {
$header = [
'x5t' => base64_encode(hex2bin($this->clientCertificateThumbprint)),
];
$now = time();
$payload = [
'aud' => "https://login.microsoftonline.com/{$this->tenant}/oauth2/v2.0/token",
'exp' => $now + 360,
'iat' => $now,
'iss' => $this->clientId,
'jti' => bin2hex(random_bytes(20)),
'nbf' => $now,
'sub' => $this->clientId,
];
$jwt = JWT::encode($payload, str_replace('\n', "\n", $this->clientCertificatePrivateKey), 'RS256', null, $header);

unset($params['client_secret']);
$params['client_assertion_type'] = 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer';
$params['client_assertion'] = $jwt;
}

return parent::getAccessTokenRequest($params);
}

/**
* @inheritdoc
*/
Expand Down

0 comments on commit 3786c93

Please sign in to comment.