Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 38
0.00% covered (danger)
0.00%
0 / 5
CRAP
0.00% covered (danger)
0.00%
0 / 1
JWT
0.00% covered (danger)
0.00%
0 / 38
0.00% covered (danger)
0.00%
0 / 5
306
0.00% covered (danger)
0.00%
0 / 1
 createSignature
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
12
 createJWT
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
12
 getHeader
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
12
 getPayload
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
12
 validateJWT
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
30
1<?php
2/**
3 * Jingga
4 *
5 * PHP Version 8.1
6 *
7 * @package   phpOMS\Auth
8 * @copyright Dennis Eichhorn
9 * @license   OMS License 2.0
10 * @version   1.0.0
11 * @link      https://jingga.app
12 */
13declare(strict_types=1);
14
15namespace phpOMS\Session;
16
17use phpOMS\Utils\Encoding\Base64Url;
18
19/**
20 * JWT class.
21 *
22 * Creates, parses and validates JWT tokens.
23 *
24 * Header:    base64url([algo: ..., typ: jwt])
25 * Payload:   base64url([...])
26 * Signature: hmac(Header . Payload, secret)
27 *
28 * @package phpOMS\Auth
29 * @license OMS License 2.0
30 * @link    https://jingga.app
31 * @since   1.0.0
32 */
33final class JWT
34{
35    /**
36     * Create JWT signature part
37     *
38     * @param string                                                   $secret  Secret (at least 256 bit)
39     * @param array{alg:string, typ:string}                            $header  Header
40     * @param array{sub:string, uid?:string, name?:string, iat:string} $payload Payload
41     *
42     * @return string hmac(Header64 . Payload64, secret)
43     *
44     * @since 1.0.0
45     */
46    private static function createSignature(string $secret, array $header, array $payload) : string
47    {
48        $headerJson  = \json_encode($header);
49        $payloadJson = \json_encode($payload);
50
51        if (!\is_string($headerJson) || !\is_string($payloadJson)) {
52            return '';
53        }
54
55        $header64  = Base64Url::encode($headerJson);
56        $payload64 = Base64Url::encode($payloadJson);
57
58        $algorithm = '';
59        $algorithm = 'sha256';
60
61        return \hash_hmac($algorithm, $header64 . '.' . $payload64, $secret, false);
62    }
63
64    /**
65     * Create JWT token
66     *
67     * @param string                                                   $secret  Secret (at least 256 bit)
68     * @param array{alg:string, typ:string}                            $header  Header
69     * @param array{sub:string, uid?:string, name?:string, iat:string} $payload Payload
70     *
71     * @return string Header64 . Payload64 . hmac(Header64 . Payload64, secret)
72     *
73     * @since 1.0.0
74     */
75    public static function createJWT(string $secret, array $header, array $payload) : string
76    {
77        $headerJson  = \json_encode($header);
78        $payloadJson = \json_encode($payload);
79
80        if (!\is_string($headerJson) || !\is_string($payloadJson)) {
81            return '';
82        }
83
84        $header64  = Base64Url::encode($headerJson);
85        $payload64 = Base64Url::encode($payloadJson);
86
87        $signature = self::createSignature($secret, $header, $payload);
88
89        return $header64 . $payload64 . Base64Url::encode($signature);
90    }
91
92    /**
93     * Get the header from the jwt string
94     *
95     * @param string $jwt JWT string
96     *
97     * @return array
98     *
99     * @since 1.0.0
100     */
101    public static function getHeader(string $jwt) : array
102    {
103        $explode = \explode('.', $jwt);
104
105        if (\count($explode) !== 3) {
106            return [];
107        }
108
109        $json = \json_decode(Base64Url::decode($explode[0]), true);
110
111        return \is_array($json) ? $json : [];
112    }
113
114    /**
115     * Get the payload from the jwt string
116     *
117     * @param string $jwt JWT string
118     *
119     * @return array
120     *
121     * @since 1.0.0
122     */
123    public static function getPayload(string $jwt) : array
124    {
125        $explode = \explode('.', $jwt);
126
127        if (\count($explode) !== 3) {
128            return [];
129        }
130
131        $json = \json_decode(Base64Url::decode($explode[1]), true);
132
133        return \is_array($json) ? $json : [];
134    }
135
136    /**
137     * Validate JWT token integrity
138     *
139     * @param string $secret Secret (at least 256 bit)
140     * @param string $jwt    JWT token [Header64 . Payload64 . hmac(Header64 . Payload64, secret)]
141     *
142     * @return bool
143     *
144     * @since 1.0.0
145     */
146    public static function validateJWT(string $secret, string $jwt) : bool
147    {
148        $explode = \explode('.', $jwt);
149
150        if (\count($explode) !== 3) {
151            return false;
152        }
153
154        try {
155            $header  = \json_decode(Base64Url::decode($explode[0]), true);
156            $payload = \json_decode(Base64Url::decode($explode[1]), true);
157
158            if (!\is_array($header) || !\is_array($payload)) {
159                return false;
160            }
161
162            /** @var array{alg:string, typ:string} $header */
163            /** @var array{sub:string, uid?:string, name?:string, iat:string} $payload */
164            $signature = self::createSignature($secret, $header, $payload);
165
166            return \hash_equals($signature, $explode[2]);
167        } catch (\Throwable $_) {
168            return false;
169        }
170    }
171}