PageRenderTime 48ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/src/OAuth2/GrantType/JwtBearer.php

https://github.com/rich-choy/oauth2-server-php
PHP | 192 lines | 115 code | 27 blank | 50 comment | 18 complexity | 7baae961564a8d0815ce4bd6a1335261 MD5 | raw file
Possible License(s): MIT
  1. <?php
  2. namespace OAuth2\GrantType;
  3. use OAuth2\ClientAssertionType\ClientAssertionTypeInterface;
  4. use OAuth2\Storage\JwtBearerInterface;
  5. use OAuth2\Encryption\Jwt;
  6. use OAuth2\ResponseType\AccessTokenInterface;
  7. use OAuth2\RequestInterface;
  8. use OAuth2\ResponseInterface;
  9. /**
  10. * The JWT bearer authorization grant implements JWT (JSON Web Tokens) as a grant type per the IETF draft.
  11. *
  12. * @see http://tools.ietf.org/html/draft-ietf-oauth-jwt-bearer-04#section-4
  13. *
  14. * @author F21
  15. * @author Brent Shaffer <bshafs at gmail dot com>
  16. */
  17. class JwtBearer implements GrantTypeInterface, ClientAssertionTypeInterface
  18. {
  19. private $jwt;
  20. protected $storage;
  21. protected $audience;
  22. protected $jwtUtil;
  23. /**
  24. * Creates an instance of the JWT bearer grant type.
  25. *
  26. * @param OAuth2_Storage_JWTBearerInterface $storage
  27. * A valid storage interface that implements storage hooks for the JWT bearer grant type.
  28. * @param string $audience
  29. * The audience to validate the token against. This is usually the full URI of the OAuth token requests endpoint.
  30. * @param OAuth2_Encryption_JWT OPTIONAL $jwtUtil
  31. * The class used to decode, encode and verify JWTs.
  32. */
  33. public function __construct(JwtBearerInterface $storage, $audience, Jwt $jwtUtil = null)
  34. {
  35. $this->storage = $storage;
  36. $this->audience = $audience;
  37. if (is_null($jwtUtil)) {
  38. $jwtUtil = new Jwt();
  39. }
  40. $this->jwtUtil = $jwtUtil;
  41. }
  42. /**
  43. * Returns the grant_type get parameter to identify the grant type request as JWT bearer authorization grant.
  44. *
  45. * @return
  46. * The string identifier for grant_type.
  47. *
  48. * @see OAuth2_GrantTypeInterface::getQuerystringIdentifier()
  49. */
  50. public function getQuerystringIdentifier()
  51. {
  52. return 'urn:ietf:params:oauth:grant-type:jwt-bearer';
  53. }
  54. /**
  55. * Validates the data from the decoded JWT.
  56. *
  57. * @return
  58. * TRUE if the JWT request is valid and can be decoded. Otherwise, FALSE is returned.
  59. *
  60. * @see OAuth2_GrantTypeInterface::getTokenData()
  61. */
  62. public function validateRequest(RequestInterface $request, ResponseInterface $response)
  63. {
  64. if (!$request->request("assertion")) {
  65. $response->setError(400, 'invalid_request', 'Missing parameters: "assertion" required');
  66. return null;
  67. }
  68. // Store the undecoded JWT for later use
  69. $undecodedJWT = $request->request('assertion');
  70. // Decode the JWT
  71. $jwt = $this->jwtUtil->decode($request->request('assertion'), null, false);
  72. if (!$jwt) {
  73. $response->setError(400, 'invalid_request', "JWT is malformed");
  74. return null;
  75. }
  76. // ensure these properties contain a value
  77. // @todo: throw malformed error for missing properties
  78. $jwt = array_merge(array(
  79. 'scope' => null,
  80. 'iss' => null,
  81. 'sub' => null,
  82. 'aud' => null,
  83. 'exp' => null,
  84. 'nbf' => null,
  85. 'iat' => null,
  86. 'jti' => null,
  87. 'typ' => null,
  88. ), $jwt);
  89. if (!isset($jwt['iss'])) {
  90. $response->setError(400, 'invalid_grant', "Invalid issuer (iss) provided");
  91. return null;
  92. }
  93. if (!isset($jwt['sub'])) {
  94. $response->setError(400, 'invalid_grant', "Invalid subject (sub) provided");
  95. return null;
  96. }
  97. if (!isset($jwt['exp'])) {
  98. $response->setError(400, 'invalid_grant', "Expiration (exp) time must be present");
  99. return null;
  100. }
  101. // Check expiration
  102. if (ctype_digit($jwt['exp'])) {
  103. if ($jwt['exp'] <= time()) {
  104. $response->setError(400, 'invalid_grant', "JWT has expired");
  105. return null;
  106. }
  107. } else {
  108. $response->setError(400, 'invalid_grant', "Expiration (exp) time must be a unix time stamp");
  109. return null;
  110. }
  111. // Check the not before time
  112. if ($notBefore = $jwt['nbf']) {
  113. if (ctype_digit($notBefore)) {
  114. if ($notBefore > time()) {
  115. $response->setError(400, 'invalid_grant', "JWT cannot be used before the Not Before (nbf) time");
  116. return null;
  117. }
  118. } else {
  119. $response->setError(400, 'invalid_grant', "Not Before (nbf) time must be a unix time stamp");
  120. return null;
  121. }
  122. }
  123. // Check the audience if required to match
  124. if (!isset($jwt['aud']) || ($jwt['aud'] != $this->audience)) {
  125. $response->setError(400, 'invalid_grant', "Invalid audience (aud)");
  126. return null;
  127. }
  128. // Get the iss's public key
  129. // @see http://tools.ietf.org/html/draft-ietf-oauth-json-web-token-06#section-4.1.1
  130. if (!$key = $this->storage->getClientKey($jwt['iss'], $jwt['sub'])) {
  131. $response->setError(400, 'invalid_grant', "Invalid issuer (iss) or subject (sub) provided");
  132. return null;
  133. }
  134. // Verify the JWT
  135. if (!$this->jwtUtil->decode($undecodedJWT, $key, true)) {
  136. $response->setError(400, 'invalid_grant', "JWT failed signature verification");
  137. return null;
  138. }
  139. $this->jwt = $jwt;
  140. return true;
  141. }
  142. public function getClientId()
  143. {
  144. return $this->jwt['iss'];
  145. }
  146. public function getUserId()
  147. {
  148. return $this->jwt['sub'];
  149. }
  150. public function getScope()
  151. {
  152. return $this->jwt['scope'];
  153. }
  154. /**
  155. * Creates an access token that is NOT associated with a refresh token.
  156. * If a subject (sub) the name of the user/account we are accessing data on behalf of.
  157. *
  158. * @see OAuth2_GrantTypeInterface::createAccessToken()
  159. */
  160. public function createAccessToken(AccessTokenInterface $accessToken, $client_id, $user_id, $scope)
  161. {
  162. $includeRefreshToken = false;
  163. return $accessToken->createAccessToken($client_id, $user_id, $scope, $includeRefreshToken);
  164. }
  165. }