Skip to content

Commit

Permalink
Merge pull request #853 from lcobucci/facade-improvements
Browse files Browse the repository at this point in the history
Add documentation for JwtFacade
  • Loading branch information
lcobucci authored Jul 24, 2022
2 parents fb6c1b6 + 0826b1e commit fa8cbfc
Show file tree
Hide file tree
Showing 9 changed files with 259 additions and 29 deletions.
6 changes: 3 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@ vendor/composer/installed.json: composer.json composer.lock

.PHONY: phpunit
phpunit:
@php -d zend.assertions=1 vendor/bin/phpunit
@php -d assert.exception=1 -d zend.assertions=1 vendor/bin/phpunit

.PHONY: infection
infection:
@php -d zend.assertions=1 vendor/bin/phpunit --testsuite=unit --coverage-xml=build/coverage-xml --log-junit=build/junit.xml $(PHPUNIT_FLAGS)
@php -d zend.assertions=1 vendor/bin/infection -v -s --threads=$(PARALLELISM) --coverage=build --skip-initial-tests $(INFECTION_FLAGS)
@php -d assert.exception=1 -d zend.assertions=1 vendor/bin/phpunit --testsuite=unit --coverage-xml=build/coverage-xml --log-junit=build/junit.xml $(PHPUNIT_FLAGS)
@php -d assert.exception=1 -d zend.assertions=1 vendor/bin/infection -v -s --threads=$(PARALLELISM) --coverage=build --skip-initial-tests $(INFECTION_FLAGS)

.PHONY: phpcbf
phpcbf:
Expand Down
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,8 @@
"allow-plugins": {
"dealerdirect/phpcodesniffer-composer-installer": true,
"phpstan/extension-installer": true,
"infection/extension-installer": true
"infection/extension-installer": true,
"ocramius/package-versions": true
},
"preferred-install": "dist",
"sort-packages": true
Expand Down
137 changes: 137 additions & 0 deletions docs/quick-start.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
# Quick start

Once the library has been [installed](installation.md), you are able to issue and parse JWTs.
The class `Lcobucci\JWT\JwtFacade` is the quickest way to perform these operations.

Using that facade we also aim to make sure that every token is properly signed and has the recommended claims for date control.

## Issuing tokens

The method `Lcobucci\JWT\JwtFacade#issue()` is available for quickly creating tokens.
It uses the current time to generate the date claims (default expiration is **5 minutes**).

To issue a token, call the method passing: an algorithm, a key, and a customisation function:

```php
<?php
declare(strict_types=1);

namespace MyApp;

require 'vendor/autoload.php';

use DateTimeImmutable;
use Lcobucci\JWT\Builder;
use Lcobucci\JWT\JwtFacade;
use Lcobucci\JWT\Signer\Hmac\Sha256;
use Lcobucci\JWT\Signer\Key\InMemory;

use function var_dump;

$key = InMemory::base64Encoded(
'hiG8DlOKvtih6AxlZn5XKImZ06yu8I3mkOzaJrEuW8yAv8Jnkw330uMt8AEqQ5LB'
);

$token = (new JwtFacade())->issue(
new Sha256(),
$key,
static fn (
Builder $builder,
DateTimeImmutable $issuedAt
): Builder => $builder
->issuedBy('https://api.my-awesome-app.io')
->permittedFor('https://client-app.io')
->expiresAt($issuedAt->modify('+10 minutes'))
);

var_dump($token->claims()->all());
echo $token->toString();
```

### Creating tokens during tests

To reduce the chance of having flaky tests on your test suite, the facade supports the usage of a clock object.
That allows passing an implementation that always returns the same point in time.

You can achieve that by specifying the `clock` constructor parameter:

```php
<?php
declare(strict_types=1);

namespace MyApp;

require 'vendor/autoload.php';

use DateTimeImmutable;
use Lcobucci\Clock\FrozenClock;
use Lcobucci\JWT\Builder;
use Lcobucci\JWT\JwtFacade;
use Lcobucci\JWT\Signer\Hmac\Sha256;
use Lcobucci\JWT\Signer\Key\InMemory;
use Lcobucci\JWT\Token\RegisteredClaims;

$clock = new FrozenClock(new DateTimeImmutable('2022-06-24 22:51:10'));
$key = InMemory::base64Encoded(
'hiG8DlOKvtih6AxlZn5XKImZ06yu8I3mkOzaJrEuW8yAv8Jnkw330uMt8AEqQ5LB'
);

$token = (new JwtFacade(null, $clock))->issue(
new Sha256(),
$key,
static fn (
Builder $builder,
DateTimeImmutable $issuedAt
): Builder => $builder
);

echo $token->claims()->get(
RegisteredClaims::ISSUED_AT
)->format(DateTimeImmutable::RFC3339); // 2022-06-24 22:51:10
```

## Parsing tokens

The method `Lcobucci\JWT\JwtFacade#parse()` is the one for quickly parsing tokens.
It also verifies the signature and date claims, throwing an exception in case of tokens in unexpected state.

```php
<?php
declare(strict_types=1);

namespace MyApp;

require 'vendor/autoload.php';

use DateTimeImmutable;
use Lcobucci\Clock\FrozenClock;
use Lcobucci\JWT\JwtFacade;
use Lcobucci\JWT\Signer\Hmac\Sha256;
use Lcobucci\JWT\Signer\Key\InMemory;
use Lcobucci\JWT\Validation\Constraint;

use function var_dump;

$jwt = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE2NTg2OTYwNTIsIm5iZiI6MT'
. 'Y1ODY5NjA1MiwiZXhwIjoxNjU4Njk2NjUyLCJpc3MiOiJodHRwczovL2FwaS5teS1hd2Vzb'
. '21lLWFwcC5pbyIsImF1ZCI6Imh0dHBzOi8vY2xpZW50LWFwcC5pbyJ9.yzxpjyq8lXqMgaN'
. 'rMEOLUr7R0brvhwXx0gp56uWEIfc';

$key = InMemory::base64Encoded(
'hiG8DlOKvtih6AxlZn5XKImZ06yu8I3mkOzaJrEuW8yAv8Jnkw330uMt8AEqQ5LB'
);

$token = (new JwtFacade())->parse(
$jwt,
new Constraint\SignedWith(new Sha256(), $key),
new Constraint\StrictValidAt(
new FrozenClock(new DateTimeImmutable('2022-07-24 20:55:10+00:00'))
)
);

var_dump($token->claims()->all());
```

!!! Warning
The example above uses `FrozenClock` as clock implementation to make sure that code will always work.
Use `SystemClock` on the production code of your application, allowing the parser to correctly verify the date claims.
3 changes: 0 additions & 3 deletions infection.json.dist
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,6 @@
"InstanceOf_": {
"ignore": [
"Lcobucci\\JWT\\Signer\\OpenSSL::freeKey"
],
"ignoreSourceCodeByRegex": [
"assert\\(.+\\);"
]
},
"LessThanOrEqualTo": {
Expand Down
4 changes: 4 additions & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ nav:
- Intro:
- 'index.md'
- 'installation.md'
- 'quick-start.md'
- Usage:
- 'configuration.md'
- 'issuing-tokens.md'
Expand All @@ -16,3 +17,6 @@ nav:

markdown_extensions:
- admonition
- footnotes
- toc:
permalink: true
9 changes: 8 additions & 1 deletion phpcs.xml.dist
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
<?xml version="1.0"?>
<ruleset>
<ruleset
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="vendor/squizlabs/php_codesniffer/phpcs.xsd"
>
<arg name="basepath" value="." />
<arg name="extensions" value="php" />
<arg name="parallel" value="80" />
Expand All @@ -11,5 +14,9 @@
<file>test</file>

<rule ref="Lcobucci" />

<rule ref="SlevomatCodingStandard.Functions.UnusedParameter">
<exclude-pattern>test/unit/UnsupportedParser.php</exclude-pattern>
</rule>
</ruleset>

24 changes: 17 additions & 7 deletions src/JwtFacade.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@

namespace Lcobucci\JWT;

use Closure;
use DateTimeImmutable;
use Lcobucci\Clock\Clock;
use Lcobucci\Clock\SystemClock;
use Lcobucci\JWT\Encoding\ChainedFormatter;
use Lcobucci\JWT\Encoding\JoseEncoder;
use Lcobucci\JWT\Signer\Key;
use Lcobucci\JWT\Token\Parser;
use Lcobucci\JWT\Validation\Constraint;
use Lcobucci\JWT\Validation\SignedWith;
use Lcobucci\JWT\Validation\ValidAt;
Expand All @@ -17,21 +19,30 @@

final class JwtFacade
{
/** @param callable(Builder):Builder $customiseBuilder */
private Parser $parser;
private Clock $clock;

public function __construct(?Parser $parser = null, ?Clock $clock = null)
{
$this->parser = $parser ?? new Token\Parser(new JoseEncoder());
$this->clock = $clock ?? SystemClock::fromSystemTimezone();
}

/** @param Closure(Builder, DateTimeImmutable):Builder $customiseBuilder */
public function issue(
Signer $signer,
Key $signingKey,
callable $customiseBuilder
Closure $customiseBuilder
): UnencryptedToken {
$builder = new Token\Builder(new JoseEncoder(), ChainedFormatter::withUnixTimestampDates());

$now = new DateTimeImmutable();
$now = $this->clock->now();
$builder
->issuedAt($now)
->canOnlyBeUsedAfter($now)
->expiresAt($now->modify('+5 minutes'));

return $customiseBuilder($builder)->getToken($signer, $signingKey);
return $customiseBuilder($builder, $now)->getToken($signer, $signingKey);
}

public function parse(
Expand All @@ -40,8 +51,7 @@ public function parse(
ValidAt $validAt,
Constraint ...$constraints
): UnencryptedToken {
$token = (new Parser(new JoseEncoder()))->parse($jwt);

$token = $this->parser->parse($jwt);
assert($token instanceof UnencryptedToken);

(new Validator())->assert(
Expand Down
42 changes: 28 additions & 14 deletions test/unit/JwtFacadeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

namespace Lcobucci\JWT;

use DateInterval;
use AssertionError;
use DateTimeImmutable;
use Lcobucci\Clock\FrozenClock;
use Lcobucci\JWT\Signer\Hmac\Sha256;
Expand All @@ -17,6 +17,7 @@
use PHPUnit\Framework\TestCase;

/**
* @covers ::__construct
* @coversDefaultClass \Lcobucci\JWT\JwtFacade
*
* @uses \Lcobucci\JWT\Token\Parser
Expand Down Expand Up @@ -56,14 +57,12 @@ protected function setUp(): void

private function createToken(): string
{
return (new JwtFacade())->issue(
return (new JwtFacade(null, $this->clock))->issue(
$this->signer,
$this->key,
function (Builder $builder): Builder {
function (Builder $builder, DateTimeImmutable $issuedAt): Builder {
return $builder
->issuedAt($this->clock->now())
->canOnlyBeUsedAfter($this->clock->now())
->expiresAt($this->clock->now()->add(new DateInterval('PT5M')))
->expiresAt($issuedAt->modify('+5 minutes'))
->issuedBy($this->issuer);
}
)->toString();
Expand All @@ -77,27 +76,25 @@ function (Builder $builder): Builder {
*/
public function issueSetTimeValidity(): void
{
$token = (new JwtFacade())->issue(
$token = (new JwtFacade(null, $this->clock))->issue(
$this->signer,
$this->key,
static function (Builder $builder): Builder {
return $builder;
}
static fn (Builder $builder): Builder => $builder
);

$now = (new DateTimeImmutable())->modify('+30 seconds');
$now = $this->clock->now();

self::assertTrue($token->hasBeenIssuedBefore($now));
self::assertTrue($token->isMinimumTimeBefore($now));
self::assertFalse($token->isExpired($now));

$aYearAgo = (new DateTimeImmutable())->modify('-1 year');
$aYearAgo = $now->modify('-1 year');

self::assertFalse($token->hasBeenIssuedBefore($aYearAgo));
self::assertFalse($token->isMinimumTimeBefore($aYearAgo));
self::assertFalse($token->isExpired($aYearAgo));

$inOneYear = (new DateTimeImmutable())->modify('+1 year');
$inOneYear = $now->modify('+1 year');

self::assertTrue($token->hasBeenIssuedBefore($inOneYear));
self::assertTrue($token->isMinimumTimeBefore($inOneYear));
Expand Down Expand Up @@ -208,7 +205,7 @@ public function badKey(): void
public function badTime(): void
{
$token = $this->createToken();
$this->clock->setTo($this->clock->now()->add(new DateInterval('P30D')));
$this->clock->setTo($this->clock->now()->modify('+30 days'));

$this->expectException(RequiredConstraintsViolated::class);
$this->expectExceptionMessage('The token is expired');
Expand Down Expand Up @@ -239,4 +236,21 @@ public function badIssuer(): void
new IssuedBy('xyz')
);
}

/**
* @test
*
* @covers ::parse
*/
public function parserForNonUnencryptedTokens(): void
{
$this->expectException(AssertionError::class);

(new JwtFacade(new UnsupportedParser()))->parse(
'a.very-broken.token',
new SignedWith($this->signer, $this->key),
new StrictValidAt($this->clock),
new IssuedBy($this->issuer)
);
}
}
Loading

0 comments on commit fa8cbfc

Please sign in to comment.