1 | <?php |
||
2 | |||
3 | declare(strict_types=1); |
||
4 | |||
5 | namespace Jasny\Auth\Session; |
||
6 | |||
7 | use Jasny\Auth\Session\Jwt\Cookie; |
||
8 | use Jasny\Auth\Session\Jwt\CookieInterface; |
||
9 | use Jasny\Immutable; |
||
10 | use Lcobucci\JWT\Configuration; |
||
11 | |||
12 | /** |
||
13 | * Use JSON Web Token and JSON Web Signature (RFC 7519) to store auth session info. |
||
14 | * |
||
15 | * @see https://github.com/lcobucci/jwt |
||
16 | */ |
||
17 | class Jwt implements SessionInterface |
||
18 | { |
||
19 | use Immutable\With; |
||
20 | |||
21 | protected Configuration $jwtConfig; |
||
22 | |||
23 | protected int $ttl = 24 * 3600; |
||
24 | protected CookieInterface $cookie; |
||
25 | |||
26 | /** |
||
27 | * JWT constructor. |
||
28 | */ |
||
29 | 7 | public function __construct(Configuration $jwtConfig) |
|
30 | { |
||
31 | 7 | $this->jwtConfig = $jwtConfig; |
|
32 | 7 | $this->cookie = new Cookie('jwt'); |
|
33 | 7 | } |
|
34 | |||
35 | /** |
||
36 | * Get a copy with a different TTL for expiry. |
||
37 | */ |
||
38 | 1 | public function withTtl(int $seconds): self |
|
39 | { |
||
40 | 1 | return $this->withProperty('ttl', $seconds); |
|
41 | } |
||
42 | |||
43 | /** |
||
44 | * Get a copy with custom cookie handler. |
||
45 | */ |
||
46 | 7 | public function withCookie(CookieInterface $cookie): self |
|
47 | { |
||
48 | 7 | return $this->withProperty('cookie', $cookie); |
|
49 | } |
||
50 | |||
51 | /** |
||
52 | * @inheritDoc |
||
53 | */ |
||
54 | 4 | public function getInfo(): array |
|
55 | { |
||
56 | 4 | $jwt = $this->cookie->get(); |
|
57 | |||
58 | 4 | $token = $jwt !== null && $jwt !== '' |
|
59 | 3 | ? $this->jwtConfig->parser()->parse($jwt) |
|
60 | 4 | : null; |
|
61 | |||
62 | 4 | $constraints = $this->jwtConfig->validationConstraints(); |
|
63 | |||
64 | if ( |
||
65 | 4 | $token === null || |
|
66 | 4 | ($constraints !== [] && !$this->jwtConfig->validator()->validate($token, ...$constraints)) |
|
67 | ) { |
||
68 | 2 | return ['user' => null, 'context' => null, 'checksum' => null, 'timestamp' => null]; |
|
69 | } |
||
70 | |||
71 | 2 | $timestamp = $token->headers()->get('iat'); |
|
72 | 2 | if (is_array($timestamp)) { |
|
73 | 1 | $timestamp = new \DateTimeImmutable($timestamp['date'], new \DateTimeZone($timestamp['timezone'])); |
|
74 | } |
||
75 | |||
76 | return [ |
||
77 | 2 | 'user' => $token->claims()->get('user'), |
|
0 ignored issues
–
show
introduced
by
![]() |
|||
78 | 2 | 'context' => $token->claims()->get('context'), |
|
0 ignored issues
–
show
|
|||
79 | 2 | 'checksum' => $token->claims()->get('checksum'), |
|
0 ignored issues
–
show
|
|||
80 | 2 | 'timestamp' => $timestamp, |
|
81 | ]; |
||
82 | } |
||
83 | |||
84 | /** |
||
85 | * @inheritDoc |
||
86 | */ |
||
87 | 2 | public function persist($userId, $contextId, ?string $checksum, ?\DateTimeInterface $timestamp): void |
|
88 | { |
||
89 | 2 | $builder = clone $this->jwtConfig->builder(); |
|
90 | |||
91 | 2 | if ($timestamp instanceof \DateTime) { |
|
92 | 2 | $timestamp = \DateTimeImmutable::createFromMutable($timestamp); |
|
93 | } |
||
94 | 2 | $time = $timestamp ?? new \DateTimeImmutable(); |
|
95 | 2 | $expire = $time->add(new \DateInterval("PT{$this->ttl}S")); |
|
96 | |||
97 | $builder |
||
98 | 2 | ->withClaim('user', $userId) |
|
99 | 2 | ->withClaim('context', $contextId) |
|
100 | 2 | ->withClaim('checksum', $checksum) |
|
101 | 2 | ->issuedAt($time) |
|
0 ignored issues
–
show
|
|||
102 | 2 | ->expiresAt($expire); |
|
103 | |||
104 | 2 | if ($timestamp !== null) { |
|
105 | 2 | $builder->withHeader('iat', $timestamp); |
|
106 | } |
||
107 | |||
108 | 2 | $this->cookie->set( |
|
109 | 2 | $builder->getToken($this->jwtConfig->signer(), $this->jwtConfig->signingKey())->toString(), |
|
110 | 2 | $expire->getTimestamp() |
|
111 | ); |
||
112 | 2 | } |
|
113 | |||
114 | /** |
||
115 | * @inheritDoc |
||
116 | */ |
||
117 | 1 | public function clear(): void |
|
118 | { |
||
119 | 1 | $this->cookie->clear(); |
|
120 | 1 | } |
|
121 | } |
||
122 |