16
16
<p >
17
17
TODO more information about JWTs, link to RFC 7519, our docs...
18
18
</p >
19
- <div class =" alert alert-warning row" >
20
- <div >
21
- <i class =" glyphicon glyphicon-exclamation-sign" style =" font-size : 3em " title =" Warning" ></i >
22
- </div >
19
+ <div class =" alert alert-warning d-flex align-items-center" >
20
+ <i class =" glyphicon glyphicon-exclamation-sign" style =" font-size : 3em " title =" Warning" ></i >
23
21
<div class =" mx-3" >
24
22
<strong >This tool does not send JWTs to any server, it only decodes the JWT locally in your browser.</strong ><br />
25
23
Always be cautious when pasting JWTs, as they may contain credentials or sensitive information.
26
24
</div >
27
25
</div >
28
26
<div class =" jwt-decoder-container container" >
29
27
<div class =" row align-items-stretch" >
30
- <div class =" col" >
28
+ <div class =" col-6 " >
31
29
<h3 >Encoded JWT</h3 >
32
30
<div class =" h-100 d-flex flex-column" >
33
31
<div class =" form-group flex-grow-1" >
44
42
</div >
45
43
</div >
46
44
</div >
47
- <div class =" col" >
45
+ <div class =" col-6 " >
48
46
<h3 >Decoded JWT</h3 >
49
47
<div class =" jwt-decoded-output" >
50
48
<h4 >Header</h4 >
53
51
<pre id =" jwt-payload" class =" bg-dark text-light p-2" >  ; </pre >
54
52
<h4 >Signature</h4 >
55
53
<pre id =" jwt-signature" class =" bg-dark text-light p-2" >  ; </pre >
54
+ <div class =" jwt-signature-validation-result alert alert-success align-items-center d-none" >
55
+ <i class =" glyphicon glyphicon-ok-sign" style =" font-size : 3em " title =" Valid signature" ></i >
56
+ <div class =" result-message mx-3" >This JWT has a valid signature.</div >
57
+ </div >
58
+ <div class =" jwt-signature-validation-result alert alert-danger align-items-center d-none" >
59
+ <i class =" glyphicon glyphicon-remove-sign" style =" font-size : 3em " title =" Invalid signature" ></i >
60
+ <div class =" result-message mx-3" >This JWT has an invalid signature.</div >
61
+ </div >
62
+ <div class =" jwt-signature-validation-result alert alert-warning align-items-center d-none" >
63
+ <i class =" glyphicon glyphicon-question-sign" style =" font-size : 3em " title =" Validation failed" ></i >
64
+ <div class =" result-message mx-3" >Signature validation failed.</div >
65
+ </div >
56
66
<div class =" jwt-decoder-error d-none" >
57
67
<pre id =" jwt-decoder-error-message" class =" alert-danger p-2" >  ; </pre >
58
68
</div >
108
118
109
119
async function showDecodedJwt (jwtParts , header , payload , signature ) {
110
120
clearError ();
121
+ hideSignatureValidationResults ();
122
+
111
123
$ (' #jwt-header' ).text (header ? JSON .stringify (header, null , 2 ) : ' ' );
112
124
$ (' #jwt-payload' ).text (payload ? JSON .stringify (payload, null , 2 ) : ' ' );
113
125
$ (' #jwt-signature' ).text (signature || ' ' );
114
126
115
127
if (jwtParts && Array .isArray (jwtParts) && jwtParts .length === 3 ) {
116
- await attemptSignatureValidation (jwtParts);
128
+ await attemptSignatureValidation (header, jwtParts);
117
129
}
118
130
}
119
131
120
- async function attemptSignatureValidation (jwtParts ) {
132
+ async function attemptSignatureValidation (header , jwtParts ) {
121
133
const jwksUrl = $ (' #jwks-url' ).val ().trim ();
122
134
123
135
if (jwksUrl) {
124
136
await loadJwks (jwksUrl);
125
137
126
138
if (jwks .keys .length === 0 ) {
139
+ showSignatureValidationResult (' warning' , ' No JWKs loaded. Cannot validate signature.' );
127
140
return ;
128
141
}
129
142
130
143
const headerAndPayload = jwtParts[0 ] + ' .' + jwtParts[1 ];
131
144
const signature = jwtParts[2 ];
132
- // const isValid = await validateSignature(headerAndPayload, signature, jwks.keys);
145
+ const result = await validateSignature (header, headerAndPayload, signature, jwks .keys );
146
+
147
+ if (result .signatureValidated ) {
148
+ if (result .isValid ) {
149
+ showSignatureValidationResult (' success' );
150
+ } else {
151
+ showSignatureValidationResult (' danger' );
152
+ }
153
+ }
154
+ else {
155
+ showSignatureValidationResult (' warning' , result .errorMessage || ' Signature validation failed.' );
156
+ }
133
157
}
134
158
}
135
159
160
+ async function validateSignature (header , headerAndPayload , signature , keys ) {
161
+ try {
162
+ if (! header || ! header .alg ) {
163
+ return { signatureValidated: false , isValid: false , errorMessage: ' JWT header does not contain an algorithm.' };
164
+ }
165
+
166
+ if (! header .kid ) {
167
+ return { signatureValidated: false , isValid: false , errorMessage: ' JWT kid is missing.' };
168
+ }
169
+
170
+ const key = keys .find (k => k .kid && k .kid === header .kid );
171
+ if (! key) {
172
+ return { signatureValidated: false , isValid: false , errorMessage: ` No matching key found for kid: ${ header .kid } ` };
173
+ }
174
+
175
+ if (key .kty !== ' RSA' && key .kty !== ' EC' ) {
176
+ return { signatureValidated: false , isValid: false , errorMessage: ` Unsupported key type: ${ key .kty } . Only RSA and EC keys are supported.` };
177
+ }
178
+
179
+ const algorithmType = header .alg ;
180
+
181
+ // algorithmType can be RS256, RS384, RS512, ES256, ES384, ES512, PS256, PS384, PS512, etc.
182
+ if (! algorithmType || (key .kty === ' RSA' && ! algorithmType .startsWith (' RS' )) && (key .kty === ' EC' && ! algorithmType .startsWith (' ES' ))) {
183
+ return { signatureValidated: false , isValid: false , errorMessage: ` Unsupported algorithm: ${ algorithmType} . Expected RS* or PS* for RSA, or ES* for EC keys.` };
184
+ }
185
+
186
+ const algorithmName =
187
+ algorithmType .startsWith (' RS' ) ? ' RSASSA-PKCS1-v1_5' :
188
+ algorithmType .startsWith (' PS' ) ? ' RSA-PSS' :
189
+ algorithmType .startsWith (' ES' ) ? ' ECDSA' : null ;
190
+
191
+ let algorithm = { name: algorithmName };
192
+ switch (algorithmType) {
193
+ case ' RS256' :
194
+ case ' RS384' :
195
+ case ' RS512' :
196
+ algorithm .hash = { name: ' SHA-' + algorithmType .slice (2 ) };
197
+ break ;
198
+ case ' PS256' :
199
+ case ' PS384' :
200
+ case ' PS512' :
201
+ algorithm .hash = { name: ' SHA-' + algorithmType .slice (2 ) };
202
+ // Salt length is either 0 or the length of the digest algorithm that was selected when this key was created.
203
+ algorithm .saltLength = algorithmType === ' PS256' ? 32 : (algorithmType === ' PS384' ? 48 : 64 );
204
+ break ;
205
+ case ' ES256' :
206
+ algorithm .namedCurve = ' P-256' ;
207
+ break ;
208
+ case ' ES384' :
209
+ algorithm .namedCurve = ' P-384' ;
210
+ break ;
211
+ case ' ES512' :
212
+ algorithm .namedCurve = ' P-521' ;
213
+ break ;
214
+ default :
215
+ return { signatureValidated: false , isValid: false , errorMessage: ` Unsupported algorithm: ${ algorithmType} ` };
216
+ }
217
+
218
+ const subtle = window .crypto .subtle ;
219
+ const publicKey = await subtle .importKey (
220
+ ' jwk' ,
221
+ key,
222
+ algorithm,
223
+ false ,
224
+ [' verify' ]
225
+ );
226
+
227
+ signature = signature .replace (/ -/ g , ' +' ).replace (/ _/ g , ' /' ); // Replace URL-safe characters with standard Base64 characters
228
+ const binarySignature = atob (signature);
229
+ const signatureBuffer = Uint8Array .from (binarySignature, c => c .charCodeAt (0 ));
230
+ const isValid = await subtle .verify (algorithm, publicKey, signatureBuffer, new TextEncoder ().encode (headerAndPayload));
231
+ return { signatureValidated: true , isValid: isValid };
232
+ } catch (error) {
233
+ return { signatureValidated: false , isValid: false , errorMessage: ' Error validating signature: ' + error .message };
234
+ }
235
+ }
236
+
136
237
async function loadJwks (jwksUrl ) {
137
238
if (jwks .loadedFrom === jwksUrl && jwks .keys .length > 0 ) {
138
239
return ; // Already loaded
195
296
$ (' #jwt-decoder-error-message' ).text (' ' );
196
297
$ (' .jwt-decoder-error' ).addClass (' d-none' );
197
298
}
299
+
300
+ function showSignatureValidationResult (type , message ) {
301
+ hideSignatureValidationResults ();
302
+
303
+ const resultElement = $ (' .jwt-signature-validation-result.alert-' + type);
304
+ resultElement .removeClass (' d-none' ).addClass (' d-flex' );
305
+
306
+ if (message) {
307
+ resultElement .find (' .result-message' ).text (message);
308
+ }
309
+ }
310
+
311
+ function hideSignatureValidationResults () {
312
+ $ (' .jwt-signature-validation-result' ).removeClass (' d-flex' ).addClass (' d-none' );
313
+ }
198
314
</script >
199
315
}
0 commit comments