Skip to content

Commit

Permalink
Merge pull request #18 from k10r/master
Browse files Browse the repository at this point in the history
Add GrantType for JSON Web Token (JWT)
  • Loading branch information
pjcdawkins committed May 27, 2015
2 parents 7e50166 + b900be8 commit c852abd
Show file tree
Hide file tree
Showing 7 changed files with 224 additions and 3 deletions.
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
"description": "An OAuth2 plugin (subscriber) for Guzzle",
"license": "MIT",
"require": {
"guzzlehttp/guzzle": "~5.0"
"guzzlehttp/guzzle": "~5.0",
"firebase/php-jwt": "^2.0"
},
"autoload": {
"psr-4": {
Expand Down
49 changes: 47 additions & 2 deletions composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 14 additions & 0 deletions src/GrantType/GrantTypeBase.php
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,16 @@ protected function getRequired()
return ['client_id'];
}

/**
* Get additional options, if any.
*
* @return array|null
*/
protected function getAdditionalOptions()
{
return null;
}

/**
* @inheritdoc
*/
Expand All @@ -72,6 +82,10 @@ public function getToken()

$requestOptions['body'] = $body;

if ($additionalOptions = $this->getAdditionalOptions()) {
$requestOptions = array_merge_recursive($requestOptions, $additionalOptions);
}

$response = $this->client->post($config['token_url'], $requestOptions);
$data = $response->json();

Expand Down
81 changes: 81 additions & 0 deletions src/GrantType/JwtBearer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
<?php

namespace CommerceGuys\Guzzle\Oauth2\GrantType;

use GuzzleHttp\ClientInterface;
use CommerceGuys\Guzzle\Oauth2\GrantType\GrantTypeBase;
use JWT;
use SplFileObject;
use InvalidArgumentException;

/**
* JSON Web Token (JWT) Bearer Token Profiles for OAuth 2.0
*
* @link http://tools.ietf.org/html/draft-jones-oauth-jwt-bearer-04
*/
class JwtBearer extends GrantTypeBase
{
protected $grantType = 'urn:ietf:params:oauth:grant-type:jwt-bearer';

/**
* @param ClientInterface $client
* @param array $config
*/
public function __construct(ClientInterface $client, array $config = [])
{
parent::__construct($client, $config);

if (!($this->config->get('private_key') instanceof SplFileObject)) {
throw new InvalidArgumentException('private_key needs to be instance of SplFileObject');
}
}

/**
* @inheritdoc
*/
protected function getRequired()
{
return array_merge(parent::getRequired(), ['private_key']);
}

/**
* @inheritdoc
*/
protected function getAdditionalOptions()
{
return [
'body' => [
'assertion' => $this->computeJwt()
]
];
}

/**
* Compute JWT, signing with provided private key
*/
protected function computeJwt()
{
$payload = [
'iss' => $this->config->get('client_id'),
'aud' => sprintf('%s/%s', rtrim($this->client->getBaseUrl(), '/'), ltrim($this->config->get('token_url'), '/')),
'exp' => time() + 60 * 60,
'iat' => time()
];

return JWT::encode($payload, $this->readPrivateKey($this->config->get('private_key')), 'RS256');
}

/**
* Read private key
*
* @param SplFileObject $privateKey
*/
protected function readPrivateKey(SplFileObject $privateKey)
{
$key = '';
while (!$privateKey->eof()) {
$key .= $privateKey->fgets();
}
return $key;
}
}
36 changes: 36 additions & 0 deletions tests/GrantType/JwtBearerTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php

namespace CommerceGuys\Guzzle\Oauth2\Tests\GrantType;

use CommerceGuys\Guzzle\Oauth2\GrantType\JwtBearer;
use CommerceGuys\Guzzle\Oauth2\Tests\TestBase;
use SplFileObject;

class JwtBearerTest extends TestBase
{
public function testMissingConfigException()
{
$this->setExpectedException('\\InvalidArgumentException', 'Config is missing the following keys: client_id, private_key');
new JwtBearer($this->getClient());
}

public function testPrivateKeyNotSplFileObject()
{
$this->setExpectedException('\\InvalidArgumentException', 'private_key needs to be instance of SplFileObject');
$grantType = new JwtBearer($this->getClient(), [
'client_id' => 'testClient',
'private_key' => 'INVALID'
]);
}

public function testValidRequestGetsToken()
{
$grantType = new JwtBearer($this->getClient(), [
'client_id' => 'testClient',
'private_key' => new SplFileObject(__DIR__ . '/../private.key')
]);
$token = $grantType->getToken();
$this->assertNotEmpty($token->getToken());
$this->assertTrue($token->getExpires()->getTimestamp() > time());
}
}
16 changes: 16 additions & 0 deletions tests/MockOAuth2Server.php
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ protected function oauth2Token(array $request)
case 'refresh_token':
return $this->grantTypeRefreshToken($requestBody);

case 'urn:ietf:params:oauth:grant-type:jwt-bearer':
return $this->grantTypeJwtBearer($requestBody);
}
throw new \RuntimeException("Test grant type not implemented: $grantType");
}
Expand Down Expand Up @@ -140,6 +142,20 @@ protected function grantTypeRefreshToken(array $requestBody)
return $this->validTokenResponse();
}

/**
* @param array $requestBody
*
* @return array
*/
protected function grantTypeJwtBearer(array $requestBody)
{
if (!array_key_exists('assertion', $requestBody)) {
return ['status' => 401];
}

return $this->validTokenResponse();
}

/**
* @param array $request
*
Expand Down
28 changes: 28 additions & 0 deletions tests/private.key
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCTT//DLGGM1jEJ
kBPc69qjiS07nsYXwsawFChRBGu03MCUcyBr7wz6oq1jkCyJ0ZHAcTmY6bpdCpSf
JM3dt16/GRvz+edmYB/CxnnIGQmlmH+kGAAQkA1wtkZLhYYRbpdb/aDE0Azx6M8h
ibe+dOidlJJQhg1SSwPcVUVWpqs4atelf9QeQtrD92igUZhwpHjQgAE4WCrXPpaO
x/07QiUge0pWHpFBamHwoalu3JLmWEED2uiAunKiY9+VJi6GAJABzIHbsh6i1XU3
ATF2WNo7qgY2e8XlDanQypvq93qq+erwynclNJh+5yVhDCqD+IX0/knGb2MMdHTP
XiU8pLjPAgMBAAECggEAJMl/h0/X9IGwsUCnlS3Y5anl/9OAiIJ9d48xGjpOY1YV
SX0OhaWmyhhB0HE6jhglm7cquQL1JTL1NmDMgCfAo1wz3NN1c91hURSbaNrHy/Cv
P1029uviT1lVaJqphkTly3Uk5sFF2ktXHnrzxb4QMPnfJ/ix7vEIv8cTj7YDYAz9
WzdPk3dZ9HyWtt+hVc1fJSNdaqyNQyRzgiUuMCIu37j/MQ5mPWucm4siZcSzFfhl
yp8JZX0GoKtNa1zCroyDGpOFt5iVUqInQhECbfW05YFW+1KZaGInqT22hHu4TDAg
+2iiLdQnDf/ubNouMnw+wV5w922MowHGfcy2GSwaMQKBgQDDFGSVDP0KRhtfsdsk
Km9nxQTJrxwL9K69vUGsr+xraC99BmXl+IrJRP9hiYl8YC1fhkQzB6h7LfG8KGyE
V+FQQNSHuRMXtK76PU4cEqv8h0HBzxRix93dctAw8bYIfawqgPG+r3MLhBMMYy5O
Pwms6YRZcDv3SKr8/CohdZVp9QKBgQDBUN6C0TgW8lKdemRuXtEpKDlCxDzXV6nt
jPhHQ3gHVoDQkGyArZyFsa0DJmD/BOCVjCd+H2cO7EQeIJOHRxF9RerdRgfIt8lJ
MtIUH3Ehep8R3oqSFneoZkdBjNH/tXpYTGhKfPpBZPYA0++lEr72nVYs78SMUrgE
7JZX1ysJMwKBgAqvpUrc6UeUy48UaRK0GGIw0rBRnVGyV5ghM+XHxUWk8WUB4rcU
RFX+J5cqN5POmO2wpy+8bahBvgo2lKszPS5uPrYolzknNqaSkSLMiwtMRXfeZhl7
JVYqIelsdDJG4BV79sIhTkYFOB3nmPPEVD1alVto4IANRQCSt6QZktO5AoGAcpN2
vjw4rUkEdDfFbLEf8O/ZOFxM3ykjGxuRT9OKQXcgs/zVglLj0U2kiJhnpt6CKcC+
6367O1oHaX/PUL9rez9EW8+U738Wex726lxUVg5yV0n6AWn1k8bC9vP6xz8Ne2YV
7ggy3y1yrLzwbXs12b8ZA1s8uBqS3MBIv1lVNYcCgYBYNpWtAWC5bIF/TgtrShrr
Fw52T+W3Bf36codGT+E3WYlQiONDWt2H8Bd9EO1j7bVP5wk0CYdnTOKqSQTnxo0r
THS6qHcin1XW566t09dLvoxCwRCSPsP8V3oZB9W31zndZNUhnxVT+RXNUu6i3XXz
JTn6lp0rp9eHJEFV/s0Zkg==
-----END PRIVATE KEY-----

0 comments on commit c852abd

Please sign in to comment.