JWT.php 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156
  1. <?php
  2. namespace Twilio\Jwt;
  3. /**
  4. * JSON Web Token implementation
  5. *
  6. * Minimum implementation used by Realtime auth, based on this spec:
  7. * http://self-issued.info/docs/draft-jones-json-web-token-01.html.
  8. *
  9. * @author Neuman Vong <neuman@twilio.com>
  10. */
  11. class JWT {
  12. /**
  13. * @param string $jwt The JWT
  14. * @param string|null $key The secret key
  15. * @param bool $verify Don't skip verification process
  16. * @return object The JWT's payload as a PHP object
  17. * @throws \DomainException
  18. * @throws \UnexpectedValueException
  19. */
  20. public static function decode($jwt, $key = null, $verify = true) {
  21. $tks = explode('.', $jwt);
  22. if (count($tks) != 3) {
  23. throw new \UnexpectedValueException('Wrong number of segments');
  24. }
  25. list($headb64, $payloadb64, $cryptob64) = $tks;
  26. if (null === ($header = self::jsonDecode(self::urlsafeB64Decode($headb64)))
  27. ) {
  28. throw new \UnexpectedValueException('Invalid segment encoding');
  29. }
  30. if (null === $payload = self::jsonDecode(self::urlsafeB64Decode($payloadb64))
  31. ) {
  32. throw new \UnexpectedValueException('Invalid segment encoding');
  33. }
  34. $sig = self::urlsafeB64Decode($cryptob64);
  35. if ($verify) {
  36. if (empty($header->alg)) {
  37. throw new \DomainException('Empty algorithm');
  38. }
  39. if ($sig != self::sign("$headb64.$payloadb64", $key, $header->alg)) {
  40. throw new \UnexpectedValueException('Signature verification failed');
  41. }
  42. }
  43. return $payload;
  44. }
  45. /**
  46. * @param object|array $payload PHP object or array
  47. * @param string $key The secret key
  48. * @param string $algo The signing algorithm
  49. * @param array $additionalHeaders Additional keys/values to add to the header
  50. *
  51. * @return string A JWT
  52. */
  53. public static function encode($payload, $key, $algo = 'HS256', $additionalHeaders = array()) {
  54. $header = array('typ' => 'JWT', 'alg' => $algo);
  55. $header = $header + $additionalHeaders;
  56. $segments = array();
  57. $segments[] = self::urlsafeB64Encode(self::jsonEncode($header));
  58. $segments[] = self::urlsafeB64Encode(self::jsonEncode($payload));
  59. $signing_input = implode('.', $segments);
  60. $signature = self::sign($signing_input, $key, $algo);
  61. $segments[] = self::urlsafeB64Encode($signature);
  62. return implode('.', $segments);
  63. }
  64. /**
  65. * @param string $msg The message to sign
  66. * @param string $key The secret key
  67. * @param string $method The signing algorithm
  68. * @return string An encrypted message
  69. * @throws \DomainException
  70. */
  71. public static function sign($msg, $key, $method = 'HS256') {
  72. $methods = array(
  73. 'HS256' => 'sha256',
  74. 'HS384' => 'sha384',
  75. 'HS512' => 'sha512',
  76. );
  77. if (empty($methods[$method])) {
  78. throw new \DomainException('Algorithm not supported');
  79. }
  80. return hash_hmac($methods[$method], $msg, $key, true);
  81. }
  82. /**
  83. * @param string $input JSON string
  84. * @return object Object representation of JSON string
  85. * @throws \DomainException
  86. */
  87. public static function jsonDecode($input) {
  88. $obj = json_decode($input);
  89. if (function_exists('json_last_error') && $errno = json_last_error()) {
  90. self::handleJsonError($errno);
  91. } else if ($obj === null && $input !== 'null') {
  92. throw new \DomainException('Null result with non-null input');
  93. }
  94. return $obj;
  95. }
  96. /**
  97. * @param object|array $input A PHP object or array
  98. * @return string JSON representation of the PHP object or array
  99. * @throws \DomainException
  100. */
  101. public static function jsonEncode($input) {
  102. $json = json_encode($input);
  103. if (function_exists('json_last_error') && $errno = json_last_error()) {
  104. self::handleJsonError($errno);
  105. } else if ($json === 'null' && $input !== null) {
  106. throw new \DomainException('Null result with non-null input');
  107. }
  108. return $json;
  109. }
  110. /**
  111. * @param string $input A base64 encoded string
  112. *
  113. * @return string A decoded string
  114. */
  115. public static function urlsafeB64Decode($input) {
  116. $padlen = 4 - strlen($input) % 4;
  117. $input .= str_repeat('=', $padlen);
  118. return base64_decode(strtr($input, '-_', '+/'));
  119. }
  120. /**
  121. * @param string $input Anything really
  122. *
  123. * @return string The base64 encode of what you passed in
  124. */
  125. public static function urlsafeB64Encode($input) {
  126. return str_replace('=', '', strtr(base64_encode($input), '+/', '-_'));
  127. }
  128. /**
  129. * @param int $errno An error number from json_last_error()
  130. *
  131. * @throws \DomainException
  132. */
  133. private static function handleJsonError($errno) {
  134. $messages = array(
  135. JSON_ERROR_DEPTH => 'Maximum stack depth exceeded',
  136. JSON_ERROR_CTRL_CHAR => 'Unexpected control character found',
  137. JSON_ERROR_SYNTAX => 'Syntax error, malformed JSON'
  138. );
  139. throw new \DomainException(isset($messages[$errno])
  140. ? $messages[$errno]
  141. : 'Unknown JSON error: ' . $errno
  142. );
  143. }
  144. }