Parser::parse()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 13
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 6
c 1
b 0
f 0
nc 1
nop 1
dl 0
loc 13
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
namespace MiladRahimi\Jwt;
6
7
use MiladRahimi\Jwt\Base64\SafeBase64Parser;
8
use MiladRahimi\Jwt\Base64\Base64Parser;
9
use MiladRahimi\Jwt\Cryptography\Verifier;
10
use MiladRahimi\Jwt\Exceptions\InvalidSignatureException;
11
use MiladRahimi\Jwt\Exceptions\InvalidTokenException;
12
use MiladRahimi\Jwt\Exceptions\JsonDecodingException;
13
use MiladRahimi\Jwt\Exceptions\SigningException;
14
use MiladRahimi\Jwt\Exceptions\ValidationException;
15
use MiladRahimi\Jwt\Json\StrictJsonParser;
16
use MiladRahimi\Jwt\Json\JsonParser;
17
use MiladRahimi\Jwt\Validator\DefaultValidator;
18
use MiladRahimi\Jwt\Validator\Validator;
19
20
/**
21
 * The parser is responsible for verifying, decoding, and validating
22
 * JSON Web Tokens (JWTs), extracting the contained claims.
23
 */
24
class Parser
25
{
26
    private Verifier $verifier;
27
28
    private Validator $validator;
29
30
    private JsonParser $jsonParser;
31
32
    private Base64Parser $base64Parser;
33
34
    public function __construct(
35
        Verifier      $verifier,
36
        ?Validator    $validator = null,
37
        ?JsonParser   $jsonParser = null,
38
        ?Base64Parser $base64Parser = null
39
    ) {
40
        $this->verifier = $verifier;
41
        $this->validator = $validator ?: new DefaultValidator();
42
        $this->jsonParser = $jsonParser ?: new StrictJsonParser();
43
        $this->base64Parser = $base64Parser ?: new SafeBase64Parser();
44
    }
45
46
    /**
47
     * Parse (verify, decode, and validate) the JWT and extract claims
48
     *
49
     * @throws Exceptions\SigningException
50
     * @throws Exceptions\InvalidSignatureException
51
     * @throws Exceptions\InvalidTokenException
52
     * @throws Exceptions\JsonDecodingException
53
     * @throws Exceptions\ValidationException
54
     */
55
    public function parse(string $jwt): array
56
    {
57
        [$header, $payload, $signature] = $this->split($jwt);
58
59
        $this->validateHeader($header);
60
61
        $this->verifySignature($header, $payload, $signature);
62
63
        $claims = $this->decode($payload);
64
65
        $this->validator->validate($claims);
66
67
        return $claims;
68
    }
69
70
    /**
71
     * Split (explode) JWT to its components
72
     *
73
     * @throws Exceptions\InvalidTokenException
74
     */
75
    private function split(string $jwt): array
76
    {
77
        $sections = explode('.', $jwt);
78
79
        if (count($sections) !== 3) {
80
            throw new Exceptions\InvalidTokenException('JWT format is not valid.');
81
        }
82
83
        return $sections;
84
    }
85
86
    /**
87
     * Verify the JWT (verify the signature)
88
     *
89
     * @throws Exceptions\SigningException
90
     * @throws Exceptions\InvalidSignatureException
91
     * @throws Exceptions\InvalidTokenException
92
     */
93
    public function verify(string $jwt): void
94
    {
95
        [$header, $payload, $signature] = $this->split($jwt);
96
97
        $this->verifySignature($header, $payload, $signature);
98
    }
99
100
    /**
101
     * Verify the JWT signature
102
     *
103
     * @throws Exceptions\SigningException
104
     * @throws Exceptions\InvalidSignatureException
105
     */
106
    private function verifySignature(string $header, string $payload, string $signature): void
107
    {
108
        $signature = $this->base64Parser->decode($signature);
109
110
        $this->verifier->verify("$header.$payload", $signature);
111
    }
112
113
    /**
114
     * Decode JWT and extract claims
115
     *
116
     * @throws Exceptions\JsonDecodingException
117
     */
118
    private function decode(string $payload): array
119
    {
120
        return $this->jsonParser->decode($this->base64Parser->decode($payload));
121
    }
122
123
    /**
124
     * Validate JWT (verify signature and validate claims)
125
     *
126
     * @throws ValidationException
127
     * @throws InvalidSignatureException
128
     * @throws InvalidTokenException
129
     * @throws JsonDecodingException
130
     * @throws SigningException
131
     */
132
    public function validate(string $jwt): void
133
    {
134
        [$header, $payload, $signature] = $this->split($jwt);
135
136
        $this->verifySignature($header, $payload, $signature);
137
138
        $claims = $this->decode($payload);
139
140
        $this->validator->validate($claims);
141
    }
142
143
    /**
144
     * @throws JsonDecodingException
145
     * @throws InvalidTokenException
146
     */
147
    public function validateHeader(string $header): void
148
    {
149
        $fields = $this->jsonParser->decode($this->base64Parser->decode($header));
150
151
        if (!isset($fields['typ'])) {
152
            throw new InvalidTokenException('JWT header does not have `typ` field.');
153
        }
154
        if ($fields['typ'] !== 'JWT') {
155
            throw new InvalidTokenException("JWT of type `{$fields['typ']}` is not supported.");
156
        }
157
158
        if (isset($fields['kid'])) {
159
            if ($fields['kid'] !== $this->verifier->kid()) {
160
                throw new InvalidTokenException("The kid is not compatible with key ID.");
161
            }
162
        }
163
    }
164
165
    public function getJsonParser(): JsonParser
166
    {
167
        return $this->jsonParser;
168
    }
169
170
    public function getBase64Parser(): Base64Parser
171
    {
172
        return $this->base64Parser;
173
    }
174
175
    public function getVerifier(): Verifier
176
    {
177
        return $this->verifier;
178
    }
179
180
    public function getValidator(): Validator
181
    {
182
        return $this->validator;
183
    }
184
}
185