Passed
Push — master ( bf67e8...626507 )
by Milad
02:27
created

Parser::validateHeader()   A

Complexity

Conditions 5
Paths 5

Size

Total Lines 14
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

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