-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathTokenMiddleware.php
124 lines (114 loc) · 3.81 KB
/
TokenMiddleware.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
<?php
declare(strict_types=1);
namespace Dakujem\Middleware;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface as Handler;
use Psr\Log\LoggerInterface;
use Psr\Log\LogLevel;
/**
* This middleware detects tokens in the request and decodes them.
* Its responsibility is to provide tokens for the application.
*
* By default, it extracts raw tokens from `Authorization` header or `token` cookie,
* then decodes them as JWT (JSON Web Token) using the provided decoder,
* and finally writes the decoded tokens to the `token` attribute of the request,
* or writes an error message to `token.error` attribute in case the decoding fails.
* All steps are configurable.
*
* Uses a set of extractors to extract a raw token string,
* a decoder to decode it to a token representation
* and an injector to write the token (or error message) to a request attribute.
*
* Caveat ⚠:
* When using a Generator for the set of extractors,
* each instance of this middleware can only reliably process one Request,
* because a generator can not be restarted/rewound.
*
* @author Andrej Rypak <[email protected]>
*/
final class TokenMiddleware implements MiddlewareInterface
{
/** @var callable */
private $decoder;
/** @var callable[] */
private iterable $extractors;
/** @var callable */
private $injector;
private ?LoggerInterface $logger;
public function __construct(
callable $decoder,
?iterable $extractors = null,
?callable $injector = null,
?LoggerInterface $logger = null
) {
$this->decoder = $decoder;
$this->extractors = $extractors ?? [
TokenManipulators::headerExtractor(),
TokenManipulators::cookieExtractor(),
];
$this->injector = $injector ?? TokenManipulators::attributeInjector();
$this->logger = $logger;
}
/**
* PSR-15 middleware processing.
*
* @param Request $request
* @param Handler $next
* @return Response
*/
public function process(Request $request, Handler $next): Response
{
return $next->handle(
$this->injectRequest(
$request,
fn(): ?object => $this->decodeToken($this->extractToken($request))
)
);
}
/**
* Extract a token using extractors.
*
* @param Request $request
* @return string|null
*/
private function extractToken(Request $request): ?string
{
$i = 0;
foreach ($this->extractors as $extractor) {
$token = $extractor($request, $this->logger);
if (is_string($token) && $token !== '') {
return $token;
}
$i += 1;
}
// log if no extractor found the token
$this->logger && $this->logger->log(LogLevel::DEBUG, $i > 0 ? 'Token not found.' : 'No extractors.');
return null;
}
/**
* Decode string token to its payload (claims).
*
* If the decoder throws on error, the injector should catch the exceptions.
*
* @param string|null $token
* @return object|null payload
*/
private function decodeToken(?string $token): ?object
{
return $token !== null ? ($this->decoder)($token, $this->logger) : null;
}
/**
* Perform the decoding transaction.
* The token provider callable contains logic that either returns a decoded token, `null` or throws.
*
* @param Request $request
* @param callable $tokenProvider
* @return Request
*/
private function injectRequest(Request $request, callable $tokenProvider): Request
{
return ($this->injector)($tokenProvider, $request, $this->logger);
}
}