227 lines
7.1 KiB
PHP
227 lines
7.1 KiB
PHP
<?php
|
|
|
|
namespace OAuth2\GrantType;
|
|
|
|
use OAuth2\ClientAssertionType\ClientAssertionTypeInterface;
|
|
use OAuth2\Storage\JwtBearerInterface;
|
|
use OAuth2\Encryption\Jwt;
|
|
use OAuth2\Encryption\EncryptionInterface;
|
|
use OAuth2\ResponseType\AccessTokenInterface;
|
|
use OAuth2\RequestInterface;
|
|
use OAuth2\ResponseInterface;
|
|
|
|
/**
|
|
* The JWT bearer authorization grant implements JWT (JSON Web Tokens) as a grant type per the IETF draft.
|
|
*
|
|
* @see http://tools.ietf.org/html/draft-ietf-oauth-jwt-bearer-04#section-4
|
|
*
|
|
* @author F21
|
|
* @author Brent Shaffer <bshafs at gmail dot com>
|
|
*/
|
|
class JwtBearer implements GrantTypeInterface, ClientAssertionTypeInterface
|
|
{
|
|
private $jwt;
|
|
|
|
protected $storage;
|
|
protected $audience;
|
|
protected $jwtUtil;
|
|
protected $allowedAlgorithms;
|
|
|
|
/**
|
|
* Creates an instance of the JWT bearer grant type.
|
|
*
|
|
* @param OAuth2\Storage\JWTBearerInterface|JwtBearerInterface $storage A valid storage interface that implements storage hooks for the JWT bearer grant type.
|
|
* @param string $audience The audience to validate the token against. This is usually the full URI of the OAuth token requests endpoint.
|
|
* @param EncryptionInterface|OAuth2\Encryption\JWT $jwtUtil OPTONAL The class used to decode, encode and verify JWTs.
|
|
* @param array $config
|
|
*/
|
|
public function __construct(JwtBearerInterface $storage, $audience, EncryptionInterface $jwtUtil = null, array $config = array())
|
|
{
|
|
$this->storage = $storage;
|
|
$this->audience = $audience;
|
|
|
|
if (is_null($jwtUtil)) {
|
|
$jwtUtil = new Jwt();
|
|
}
|
|
|
|
$this->config = array_merge(array(
|
|
'allowed_algorithms' => array('RS256', 'RS384', 'RS512')
|
|
), $config);
|
|
|
|
$this->jwtUtil = $jwtUtil;
|
|
|
|
$this->allowedAlgorithms = $this->config['allowed_algorithms'];
|
|
}
|
|
|
|
/**
|
|
* Returns the grant_type get parameter to identify the grant type request as JWT bearer authorization grant.
|
|
*
|
|
* @return
|
|
* The string identifier for grant_type.
|
|
*
|
|
* @see OAuth2\GrantType\GrantTypeInterface::getQuerystringIdentifier()
|
|
*/
|
|
public function getQuerystringIdentifier()
|
|
{
|
|
return 'urn:ietf:params:oauth:grant-type:jwt-bearer';
|
|
}
|
|
|
|
/**
|
|
* Validates the data from the decoded JWT.
|
|
*
|
|
* @return
|
|
* TRUE if the JWT request is valid and can be decoded. Otherwise, FALSE is returned.
|
|
*
|
|
* @see OAuth2\GrantType\GrantTypeInterface::getTokenData()
|
|
*/
|
|
public function validateRequest(RequestInterface $request, ResponseInterface $response)
|
|
{
|
|
if (!$request->request("assertion")) {
|
|
$response->setError(400, 'invalid_request', 'Missing parameters: "assertion" required');
|
|
|
|
return null;
|
|
}
|
|
|
|
// Store the undecoded JWT for later use
|
|
$undecodedJWT = $request->request('assertion');
|
|
|
|
// Decode the JWT
|
|
$jwt = $this->jwtUtil->decode($request->request('assertion'), null, false);
|
|
|
|
if (!$jwt) {
|
|
$response->setError(400, 'invalid_request', "JWT is malformed");
|
|
|
|
return null;
|
|
}
|
|
|
|
// ensure these properties contain a value
|
|
// @todo: throw malformed error for missing properties
|
|
$jwt = array_merge(array(
|
|
'scope' => null,
|
|
'iss' => null,
|
|
'sub' => null,
|
|
'aud' => null,
|
|
'exp' => null,
|
|
'nbf' => null,
|
|
'iat' => null,
|
|
'jti' => null,
|
|
'typ' => null,
|
|
), $jwt);
|
|
|
|
if (!isset($jwt['iss'])) {
|
|
$response->setError(400, 'invalid_grant', "Invalid issuer (iss) provided");
|
|
|
|
return null;
|
|
}
|
|
|
|
if (!isset($jwt['sub'])) {
|
|
$response->setError(400, 'invalid_grant', "Invalid subject (sub) provided");
|
|
|
|
return null;
|
|
}
|
|
|
|
if (!isset($jwt['exp'])) {
|
|
$response->setError(400, 'invalid_grant', "Expiration (exp) time must be present");
|
|
|
|
return null;
|
|
}
|
|
|
|
// Check expiration
|
|
if (ctype_digit($jwt['exp'])) {
|
|
if ($jwt['exp'] <= time()) {
|
|
$response->setError(400, 'invalid_grant', "JWT has expired");
|
|
|
|
return null;
|
|
}
|
|
} else {
|
|
$response->setError(400, 'invalid_grant', "Expiration (exp) time must be a unix time stamp");
|
|
|
|
return null;
|
|
}
|
|
|
|
// Check the not before time
|
|
if ($notBefore = $jwt['nbf']) {
|
|
if (ctype_digit($notBefore)) {
|
|
if ($notBefore > time()) {
|
|
$response->setError(400, 'invalid_grant', "JWT cannot be used before the Not Before (nbf) time");
|
|
|
|
return null;
|
|
}
|
|
} else {
|
|
$response->setError(400, 'invalid_grant', "Not Before (nbf) time must be a unix time stamp");
|
|
|
|
return null;
|
|
}
|
|
}
|
|
|
|
// Check the audience if required to match
|
|
if (!isset($jwt['aud']) || ($jwt['aud'] != $this->audience)) {
|
|
$response->setError(400, 'invalid_grant', "Invalid audience (aud)");
|
|
|
|
return null;
|
|
}
|
|
|
|
// Check the jti (nonce)
|
|
// @see http://tools.ietf.org/html/draft-ietf-oauth-json-web-token-13#section-4.1.7
|
|
if (isset($jwt['jti'])) {
|
|
$jti = $this->storage->getJti($jwt['iss'], $jwt['sub'], $jwt['aud'], $jwt['exp'], $jwt['jti']);
|
|
|
|
//Reject if jti is used and jwt is still valid (exp parameter has not expired).
|
|
if ($jti && $jti['expires'] > time()) {
|
|
$response->setError(400, 'invalid_grant', "JSON Token Identifier (jti) has already been used");
|
|
|
|
return null;
|
|
} else {
|
|
$this->storage->setJti($jwt['iss'], $jwt['sub'], $jwt['aud'], $jwt['exp'], $jwt['jti']);
|
|
}
|
|
}
|
|
|
|
// Get the iss's public key
|
|
// @see http://tools.ietf.org/html/draft-ietf-oauth-json-web-token-06#section-4.1.1
|
|
if (!$key = $this->storage->getClientKey($jwt['iss'], $jwt['sub'])) {
|
|
$response->setError(400, 'invalid_grant', "Invalid issuer (iss) or subject (sub) provided");
|
|
|
|
return null;
|
|
}
|
|
|
|
// Verify the JWT
|
|
if (!$this->jwtUtil->decode($undecodedJWT, $key, $this->allowedAlgorithms)) {
|
|
$response->setError(400, 'invalid_grant', "JWT failed signature verification");
|
|
|
|
return null;
|
|
}
|
|
|
|
$this->jwt = $jwt;
|
|
|
|
return true;
|
|
}
|
|
|
|
public function getClientId()
|
|
{
|
|
return $this->jwt['iss'];
|
|
}
|
|
|
|
public function getUserId()
|
|
{
|
|
return $this->jwt['sub'];
|
|
}
|
|
|
|
public function getScope()
|
|
{
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Creates an access token that is NOT associated with a refresh token.
|
|
* If a subject (sub) the name of the user/account we are accessing data on behalf of.
|
|
*
|
|
* @see OAuth2\GrantType\GrantTypeInterface::createAccessToken()
|
|
*/
|
|
public function createAccessToken(AccessTokenInterface $accessToken, $client_id, $user_id, $scope)
|
|
{
|
|
$includeRefreshToken = false;
|
|
|
|
return $accessToken->createAccessToken($client_id, $user_id, $scope, $includeRefreshToken);
|
|
}
|
|
}
|