PluginProbe
Activity Logs, User Activity Tracking, Multisite Activity Log from Logtivity / 3.3.10
Activity Logs, User Activity Tracking, Multisite Activity Log from Logtivity v3.3.10
3.3.10 3.3.9 3.3.8 trunk 1.0 1.1.0 1.10.0 1.11.0 1.11.1 1.12.0 1.13.0 1.14.0 1.15.0 1.16.0 1.17.0 1.17.1 1.18.0 1.19.0 1.2.0 1.20.0 1.20.1 1.3.0 1.3.1 1.4.0 1.5.0 All 68 releases
logtivity / Core / Services / Logtivity_JsonWebToken.php

Logtivity_JsonWebToken.php in Activity Logs, User Activity Tracking, Multisite Activity Log from Logtivity 3.3.10, at Core/Services/Logtivity_JsonWebToken.php

200 lines 5.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /**
4 * @package Logtivity
5 * @copyright 2025-2026 Logtivity. All rights reserved
6 * @contact logtivity.io, [email protected]
7 *
8 * This file is part of Logtivity
9 */
10
11 // phpcs:disable Squiz.Classes.ValidClassName.NotCamelCaps
12
13 class Logtivity_JsonWebToken
14 {
15 /**
16 * @var string
17 */
18 protected string $algorithm = 'sha512';
19
20 /**
21 * @var int
22 */
23 protected int $expireSeconds = 5;
24
25 /**
26 * @param string $secret
27 * @param array $payload
28 * @param ?string $algorithm
29 *
30 * @return string
31 * @throws Exception
32 */
33 public function create(string $secret, array $payload = [], ?string $algorithm = null): string
34 {
35 $header = [
36 'alg' => $this->algorithm,
37 'typ' => 'JWT',
38 ];
39
40 if (empty($payload['exp'])) {
41 $payload['exp'] = time() + $this->expireSeconds;
42 }
43
44 $signInput = join('.', [
45 static::base64Encode(static::jsonEncode($header)),
46 static::base64Encode(static::jsonEncode($payload)),
47 ]);
48
49 if ($algorithm) {
50 $this->useAlgorithm($algorithm);
51 }
52 $signature = hash_hmac($this->algorithm, $signInput, $secret, true);
53
54 return $signInput . '.' . static::base64Encode($signature);
55 }
56
57 /**
58 * Parse a JWT token created by this helper
59 *
60 * @param string $token
61 * @param string $secret
62 *
63 * @return object
64 * @throws Exception
65 */
66 public function parse(string $token, string $secret): object
67 {
68 $atoms = explode('.', $token);
69 if (count($atoms) != 3) {
70 throw new Exception('Invalid JWT Token');
71
72 } else {
73 [$header64, $payload64, $signature64] = $atoms;
74
75 $signature = $this->base64Decode($signature64);
76 if (!$signature) {
77 throw new Exception('Invalid JWT Signature');
78
79 }
80
81 $payloadJson = $this->base64Decode($payload64);
82 if ($payloadJson) {
83 $hash = hash_hmac($this->algorithm, "{$header64}.{$payload64}", $secret, true);
84 if (hash_equals($hash, $signature)) {
85 $payload = $this->jsonDecode($payloadJson);
86 if (empty($payload->exp) == false && $payload->exp < time()) {
87 throw new Exception('Expired JWT token');
88 }
89
90 return $payload;
91
92 } else {
93 throw new Exception('Invalid JWT Token');
94 }
95 } else {
96 throw new Exception('Invalid JWT Payload');
97 }
98 }
99 }
100
101 /**
102 * @param string $algorithm
103 *
104 * @return $this
105 * @throws Exception
106 */
107 public function useAlgorithm(string $algorithm): self
108 {
109 if (in_array($algorithm, hash_algos(), true)) {
110 $this->algorithm = $algorithm;
111
112 return $this;
113 }
114
115 throw new Exception('Invalid Hash Algorithm');
116 }
117
118 /**
119 * Base64url encode JWT segments.
120 *
121 * JWT compact serialization requires base64url encoding for the header,
122 * payload, and binary signature. This is protocol encoding, not
123 * obfuscation.
124 *
125 * @param string $input
126 *
127 * @return string
128 */
129 protected function base64Encode(string $input): string
130 {
131 return str_replace(
132 '=',
133 '',
134 strtr(
135 // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode -- JWT base64url encoding requires Base64.
136 base64_encode($input),
137 '+/',
138 '-_'
139 )
140 );
141 }
142
143 /**
144 * Base64url decode JWT segments.
145 *
146 * JWT compact serialization stores header, payload, and signature as
147 * base64url. Strict Base64 decoding is required after translating the URL
148 * safe alphabet.
149 *
150 * @param string $input
151 *
152 * @return string
153 */
154 protected function base64Decode(string $input): string
155 {
156 $remainder = strlen($input) % 4;
157 if ($remainder) {
158 $padding = 4 - $remainder;
159 $input .= str_repeat('=', $padding);
160 }
161
162 // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode -- JWT base64url decoding requires Base64 with strict mode enabled.
163 $decoded = base64_decode(strtr($input, '-_', '+/'), true);
164
165 return false === $decoded ? '' : $decoded;
166 }
167
168 /**
169 * JSON Encode without escape characters
170 *
171 * @param array $input
172 *
173 * @return string
174 */
175 protected function jsonEncode(array $input): string
176 {
177 return json_encode($input, JSON_UNESCAPED_SLASHES);
178 }
179
180 /**
181 * @param ?string $input
182 *
183 * @return ?object
184 * @throws Exception
185 */
186 protected function jsonDecode(?string $input): ?object
187 {
188 $result = json_decode((string)$input, false, 512, JSON_BIGINT_AS_STRING);
189 if ($result && is_array($result)) {
190 $result = (object)$result;
191 }
192
193 if ($input && empty($result)) {
194 throw new Exception('Invalid JSON');
195 }
196
197 return $result ?: null;
198 }
199 }
200