-
-
Notifications
You must be signed in to change notification settings - Fork 33
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add support for the Firebase Auth Emulator when using
lcobucci/jwt
5.*
- Loading branch information
1 parent
e2be672
commit e03289a
Showing
16 changed files
with
453 additions
and
60 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Kreait\Firebase\JWT; | ||
|
||
use Stringable; | ||
|
||
final class InsecureToken implements Contract\Token, Stringable | ||
{ | ||
/** | ||
* @param array<string, mixed> $headers | ||
* @param array<string, mixed> $payload | ||
*/ | ||
private function __construct(private readonly string $encodedString, private readonly array $headers, private readonly array $payload) | ||
{ | ||
} | ||
|
||
public function __toString(): string | ||
{ | ||
return $this->toString(); | ||
} | ||
|
||
/** | ||
* @param array<string, mixed> $headers | ||
* @param array<string, mixed> $payload | ||
*/ | ||
public static function withValues(string $encodedString, array $headers, array $payload): self | ||
{ | ||
return new self($encodedString, $headers, $payload); | ||
} | ||
|
||
/** | ||
* @return array<string, mixed> | ||
*/ | ||
public function headers(): array | ||
{ | ||
return $this->headers; | ||
} | ||
|
||
/** | ||
* @return array<string, mixed> | ||
*/ | ||
public function payload(): array | ||
{ | ||
return $this->payload; | ||
} | ||
|
||
public function toString(): string | ||
{ | ||
return $this->encodedString; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,170 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Kreait\Firebase\JWT\Token; | ||
|
||
use DateTimeImmutable; | ||
use Lcobucci\JWT\Decoder; | ||
use Lcobucci\JWT\Parser as ParserInterface; | ||
use Lcobucci\JWT\Token; | ||
use Lcobucci\JWT\Token\DataSet; | ||
use Lcobucci\JWT\Token\InvalidTokenStructure; | ||
use Lcobucci\JWT\Token\Plain; | ||
use Lcobucci\JWT\Token\RegisteredClaims; | ||
use Lcobucci\JWT\Token\Signature; | ||
use Lcobucci\JWT\Token\UnsupportedHeaderFound; | ||
|
||
use function array_key_exists; | ||
use function is_array; | ||
|
||
/** | ||
* This is an almost 1:1 copy of the parser in `lcobucci/jwt`, with only the signature verification | ||
* removed; hence the name `InsecureParser`. | ||
* | ||
* @internal | ||
*/ | ||
final class InsecureParser implements ParserInterface | ||
{ | ||
private const MICROSECOND_PRECISION = 6; | ||
|
||
public function __construct(private readonly Decoder $decoder) | ||
{ | ||
} | ||
|
||
/** | ||
* @param non-empty-string $jwt | ||
*/ | ||
public function parse(string $jwt): Token | ||
{ | ||
[$encodedHeaders, $encodedClaims] = $this->splitJwt($jwt); | ||
|
||
if ($encodedHeaders === '') { | ||
throw new InvalidTokenStructure('The JWT string is missing the Header part'); | ||
} | ||
|
||
if ($encodedClaims === '') { | ||
throw new InvalidTokenStructure('The JWT string is missing the Claim part'); | ||
} | ||
|
||
$header = $this->parseHeader($encodedHeaders); | ||
|
||
return new Plain( | ||
new DataSet($header, $encodedHeaders), | ||
new DataSet($this->parseClaims($encodedClaims), $encodedClaims), | ||
new Signature('none', 'none'), | ||
); | ||
} | ||
|
||
/** | ||
* Splits the JWT string into an array. | ||
* | ||
* @param non-empty-string $jwt | ||
* | ||
* @throws InvalidTokenStructure when JWT doesn't have all parts | ||
* | ||
* @return string[] | ||
*/ | ||
private function splitJwt(string $jwt): array | ||
{ | ||
return explode('.', $jwt); | ||
} | ||
|
||
/** | ||
* Parses the header from a string. | ||
* | ||
* @param non-empty-string $data | ||
* | ||
* @throws InvalidTokenStructure when parsed content isn't an array | ||
* @throws UnsupportedHeaderFound when an invalid header is informed | ||
* | ||
* @return array<non-empty-string, mixed> | ||
*/ | ||
private function parseHeader(string $data): array | ||
{ | ||
$header = $this->decoder->jsonDecode($this->decoder->base64UrlDecode($data)); | ||
|
||
if (!is_array($header)) { | ||
throw InvalidTokenStructure::arrayExpected('headers'); | ||
} | ||
|
||
$this->guardAgainstEmptyStringKeys($header, 'headers'); | ||
|
||
if (array_key_exists('enc', $header)) { | ||
throw UnsupportedHeaderFound::encryption(); | ||
} | ||
|
||
if (!array_key_exists('typ', $header)) { | ||
$header['typ'] = 'JWT'; | ||
} | ||
|
||
return $header; | ||
} | ||
|
||
/** | ||
* Parses the claim set from a string. | ||
* | ||
* @param non-empty-string $data | ||
* | ||
* @throws InvalidTokenStructure when parsed content isn't an array or contains non-parseable dates | ||
* | ||
* @return array<non-empty-string, mixed> | ||
*/ | ||
private function parseClaims(string $data): array | ||
{ | ||
$claims = $this->decoder->jsonDecode($this->decoder->base64UrlDecode($data)); | ||
|
||
if (!is_array($claims)) { | ||
throw InvalidTokenStructure::arrayExpected('claims'); | ||
} | ||
|
||
$this->guardAgainstEmptyStringKeys($claims, 'claims'); | ||
|
||
if (array_key_exists(RegisteredClaims::AUDIENCE, $claims)) { | ||
$claims[RegisteredClaims::AUDIENCE] = (array) $claims[RegisteredClaims::AUDIENCE]; | ||
} | ||
|
||
foreach (RegisteredClaims::DATE_CLAIMS as $claim) { | ||
if (!array_key_exists($claim, $claims)) { | ||
continue; | ||
} | ||
|
||
$claims[$claim] = $this->convertDate($claims[$claim]); | ||
} | ||
|
||
return $claims; | ||
} | ||
|
||
/** | ||
* @param array<string, mixed> $array | ||
* @param non-empty-string $part | ||
* | ||
* @phpstan-assert array<non-empty-string, mixed> $array | ||
*/ | ||
private function guardAgainstEmptyStringKeys(array $array, string $part): void | ||
{ | ||
foreach ($array as $key => $value) { | ||
if ($key === '') { | ||
throw InvalidTokenStructure::arrayExpected($part); | ||
} | ||
} | ||
} | ||
|
||
/** @throws InvalidTokenStructure */ | ||
private function convertDate(int|float|string $timestamp): DateTimeImmutable | ||
{ | ||
if (!is_numeric($timestamp)) { | ||
throw InvalidTokenStructure::dateIsNotParseable($timestamp); | ||
} | ||
|
||
$normalizedTimestamp = number_format((float) $timestamp, self::MICROSECOND_PRECISION, '.', ''); | ||
|
||
$date = DateTimeImmutable::createFromFormat('U.u', $normalizedTimestamp); | ||
|
||
if ($date === false) { | ||
throw InvalidTokenStructure::dateIsNotParseable($normalizedTimestamp); | ||
} | ||
|
||
return $date; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Kreait\Firebase\JWT\Token; | ||
|
||
use Kreait\Firebase\JWT\Util; | ||
use Lcobucci\JWT\Decoder; | ||
use Lcobucci\JWT\Parser as ParserInterface; | ||
use Lcobucci\JWT\Token; | ||
use Lcobucci\JWT\Token\Parser as SecureParser; | ||
|
||
final class Parser implements ParserInterface | ||
{ | ||
private ParserInterface $parser; | ||
|
||
public function __construct(Decoder $decoder) | ||
{ | ||
if (Util::authEmulatorHost() !== '') { | ||
$this->parser = new InsecureParser($decoder); | ||
} else { | ||
$this->parser = new SecureParser($decoder); | ||
} | ||
} | ||
|
||
public function parse(string $jwt): Token | ||
{ | ||
return $this->parser->parse($jwt); | ||
} | ||
} |
Oops, something went wrong.