Skip to content

Commit

Permalink
Merge branch 'main' into issue-488-rounding
Browse files Browse the repository at this point in the history
  • Loading branch information
bshaffer committed May 23, 2023
2 parents b6105c7 + 398ccd2 commit 92389bd
Show file tree
Hide file tree
Showing 7 changed files with 111 additions and 16 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
php: [ "7.1", "7.2", "7.3", "7.4", "8.0", "8.1", "8.2" ]
php: [ "7.4", "8.0", "8.1", "8.2" ]
name: PHP ${{matrix.php }} Unit Test
steps:
- uses: actions/checkout@v2
Expand Down
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
# Changelog

## [6.5.0](https://github.com/firebase/php-jwt/compare/v6.4.0...v6.5.0) (2023-05-12)


### Bug Fixes

* allow KID of '0' ([#505](https://github.com/firebase/php-jwt/issues/505)) ([9dc46a9](https://github.com/firebase/php-jwt/commit/9dc46a9c3e5801294249cfd2554c5363c9f9326a))


### Miscellaneous Chores

* drop support for PHP 7.3 ([#495](https://github.com/firebase/php-jwt/issues/495))

## [6.4.0](https://github.com/firebase/php-jwt/compare/v6.3.2...v6.4.0) (2023-02-08)


Expand Down
74 changes: 74 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,40 @@ $decoded_array = (array) $decoded;
JWT::$leeway = 60; // $leeway in seconds
$decoded = JWT::decode($jwt, new Key($key, 'HS256'));
```
Example encode/decode headers
-------
Decoding the JWT headers without verifying the JWT first is NOT recommended, and is not supported by
this library. This is because without verifying the JWT, the header values could have been tampered with.
Any value pulled from an unverified header should be treated as if it could be any string sent in from an
attacker. If this is something you still want to do in your application for whatever reason, it's possible to
decode the header values manually simply by calling `json_decode` and `base64_decode` on the JWT
header part:
```php
use Firebase\JWT\JWT;

$key = 'example_key';
$payload = [
'iss' => 'http://example.org',
'aud' => 'http://example.com',
'iat' => 1356999524,
'nbf' => 1357000000
];

$headers = [
'x-forwarded-for' => 'www.google.com'
];

// Encode headers in the JWT string
$jwt = JWT::encode($payload, $key, 'HS256', null, $headers);

// Decode headers from the JWT string WITHOUT validation
// **IMPORTANT**: This operation is vulnerable to attacks, as the JWT has not yet been verified.
// These headers could be any value sent by an attacker.
list($headersB64, $payloadB64, $sig) = explode('.', $jwt);
$decoded = json_decode(base64_decode($headersB64), true);

print_r($decoded);
```
Example with RS256 (openssl)
----------------------------
```php
Expand Down Expand Up @@ -202,6 +236,44 @@ $decoded = JWT::decode($jwt, new Key($publicKey, 'EdDSA'));
echo "Decode:\n" . print_r((array) $decoded, true) . "\n";
````

Example with multiple keys
--------------------------
```php
use Firebase\JWT\JWT;
use Firebase\JWT\Key;

// Example RSA keys from previous example
// $privateKey1 = '...';
// $publicKey1 = '...';

// Example EdDSA keys from previous example
// $privateKey2 = '...';
// $publicKey2 = '...';

$payload = [
'iss' => 'example.org',
'aud' => 'example.com',
'iat' => 1356999524,
'nbf' => 1357000000
];

$jwt1 = JWT::encode($payload, $privateKey1, 'RS256', 'kid1');
$jwt2 = JWT::encode($payload, $privateKey2, 'EdDSA', 'kid2');
echo "Encode 1:\n" . print_r($jwt1, true) . "\n";
echo "Encode 2:\n" . print_r($jwt2, true) . "\n";

$keys = [
'kid1' => new Key($publicKey1, 'RS256'),
'kid2' => new Key($publicKey2, 'EdDSA'),
];

$decoded1 = JWT::decode($jwt1, $keys);
$decoded2 = JWT::decode($jwt2, $keys);

echo "Decode 1:\n" . print_r((array) $decoded1, true) . "\n";
echo "Decode 2:\n" . print_r((array) $decoded2, true) . "\n";
```

Using JWKs
----------

Expand Down Expand Up @@ -301,6 +373,8 @@ All exceptions in the `Firebase\JWT` namespace extend `UnexpectedValueException`
like this:

```php
use Firebase\JWT\JWT;
use UnexpectedValueException;
try {
$decoded = JWT::decode($payload, $keys);
} catch (LogicException $e) {
Expand Down
6 changes: 3 additions & 3 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
],
"license": "BSD-3-Clause",
"require": {
"php": "^7.1||^8.0"
"php": "^7.4||^8.0"
},
"suggest": {
"paragonie/sodium_compat": "Support EdDSA (Ed25519) signatures when libsodium is not present",
Expand All @@ -33,8 +33,8 @@
},
"require-dev": {
"guzzlehttp/guzzle": "^6.5||^7.4",
"phpspec/prophecy-phpunit": "^1.1",
"phpunit/phpunit": "^7.5||^9.5",
"phpspec/prophecy-phpunit": "^2.0",
"phpunit/phpunit": "^9.5",
"psr/cache": "^1.0||^2.0",
"psr/http-client": "^1.0",
"psr/http-factory": "^1.0"
Expand Down
20 changes: 12 additions & 8 deletions src/JWT.php
Original file line number Diff line number Diff line change
Expand Up @@ -69,11 +69,15 @@ class JWT
* Decodes a JWT string into a PHP object.
*
* @param string $jwt The JWT
* @param Key|array<string,Key> $keyOrKeyArray The Key or associative array of key IDs (kid) to Key objects.
* If the algorithm used is asymmetric, this is the public key
* Each Key object contains an algorithm and matching key.
* Supported algorithms are 'ES384','ES256', 'HS256', 'HS384',
* 'HS512', 'RS256', 'RS384', and 'RS512'
* @param Key|ArrayAccess<string,Key>|array<string,Key> $keyOrKeyArray The Key or associative array of key IDs
* (kid) to Key objects.
* If the algorithm used is asymmetric, this is
* the public key.
* Each Key object contains an algorithm and
* matching key.
* Supported algorithms are 'ES384','ES256',
* 'HS256', 'HS384', 'HS512', 'RS256', 'RS384'
* and 'RS512'.
*
* @return stdClass The JWT's payload as a PHP object
*
Expand Down Expand Up @@ -152,7 +156,7 @@ public static function decode(
// Check that this token has been created before 'now'. This prevents
// using tokens that have been created for later use (and haven't
// correctly used the nbf claim).
if (isset($payload->iat) && floor($payload->iat) > ($timestamp + static::$leeway)) {
if (!isset($payload->nbf) && isset($payload->iat) && floor($payload->iat) > ($timestamp + static::$leeway)) {
throw new BeforeValidException(
'Cannot handle token prior to ' . \date(DateTime::ISO8601, (int) $payload->iat)
);
Expand Down Expand Up @@ -376,7 +380,7 @@ public static function jsonEncode(array $input): string
}
if ($errno = \json_last_error()) {
self::handleJsonError($errno);
} elseif ($json === 'null' && $input !== null) {
} elseif ($json === 'null') {
throw new DomainException('Null result with non-null input');
}
if ($json === false) {
Expand Down Expand Up @@ -435,7 +439,7 @@ private static function getKey(
return $keyOrKeyArray;
}

if (empty($kid)) {
if (empty($kid) && $kid !== '0') {
throw new UnexpectedValueException('"kid" empty, unable to lookup correct key');
}

Expand Down
3 changes: 3 additions & 0 deletions tests/CachedKeySetTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
use OutOfBoundsException;
use PHPUnit\Framework\TestCase;
use Prophecy\Argument;
use Prophecy\PhpUnit\ProphecyTrait;
use Psr\Cache\CacheItemInterface;
use Psr\Cache\CacheItemPoolInterface;
use Psr\Http\Client\ClientInterface;
Expand All @@ -14,6 +15,8 @@

class CachedKeySetTest extends TestCase
{
use ProphecyTrait;

private $testJwksUri = 'https://jwk.uri';
private $testJwksUriKey = 'jwkshttpsjwk.uri';
private $testJwks1 = '{"keys": [{"kid":"foo","kty":"RSA","alg":"foo","n":"","e":""}]}';
Expand Down
10 changes: 6 additions & 4 deletions tests/JWTTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -204,10 +204,11 @@ public function testEmptyKeyFails()
public function testKIDChooser()
{
$keys = [
'1' => new Key('my_key', 'HS256'),
'0' => new Key('my_key0', 'HS256'),
'1' => new Key('my_key1', 'HS256'),
'2' => new Key('my_key2', 'HS256')
];
$msg = JWT::encode(['message' => 'abc'], $keys['1']->getKeyMaterial(), 'HS256', '1');
$msg = JWT::encode(['message' => 'abc'], $keys['0']->getKeyMaterial(), 'HS256', '0');
$decoded = JWT::decode($msg, $keys);
$expected = new stdClass();
$expected->message = 'abc';
Expand All @@ -217,10 +218,11 @@ public function testKIDChooser()
public function testArrayAccessKIDChooser()
{
$keys = new ArrayObject([
'1' => new Key('my_key', 'HS256'),
'0' => new Key('my_key0', 'HS256'),
'1' => new Key('my_key1', 'HS256'),
'2' => new Key('my_key2', 'HS256'),
]);
$msg = JWT::encode(['message' => 'abc'], $keys['1']->getKeyMaterial(), 'HS256', '1');
$msg = JWT::encode(['message' => 'abc'], $keys['0']->getKeyMaterial(), 'HS256', '0');
$decoded = JWT::decode($msg, $keys);
$expected = new stdClass();
$expected->message = 'abc';
Expand Down

0 comments on commit 92389bd

Please sign in to comment.