Skip to content

Commit 386c0c1

Browse files
Merge pull request #25 from DuendeSoftware/wca/jwt-decoder-feedback
Improved the JWT decoder based on feedback
2 parents 0a4ae42 + 67bdb16 commit 386c0c1

File tree

3 files changed

+95
-8
lines changed

3 files changed

+95
-8
lines changed

src/Pages/Home/JwtDecoder/JwtDecoder.cshtml

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
Show claim information
3030
</label>
3131
</div>
32-
<div class="custom-control custom-switch custom-control-inline">
32+
<div class="custom-control custom-switch custom-control-inline d-none d-md-inline-flex">
3333
<input class="custom-control-input" type="checkbox" id="togglePresenterMode">
3434
<label class="custom-control-label" for="togglePresenterMode">
3535
Presenter mode
@@ -43,11 +43,11 @@
4343
<div class="h-100 d-flex flex-column">
4444
<div class="form-group flex-grow-1">
4545
<label for="jwt-input" class="sr-only">Paste your JWT here...</label>
46-
<div id="jwt-input" class="form-control bg-dark text-light p-2 h-100 jwt-input-editable" contenteditable="true" rows="8" style="min-height: 10em;" placeholder="Paste your JWT here..."></div>
46+
<div id="jwt-input" class="form-control bg-dark text-light p-2 h-100 jwt-input-editable" contenteditable="true" rows="8" style="min-height: 10em;" placeholder="Paste your JWT here...">@Model.View?.Token</div>
4747
</div>
4848
<div class="form-group">
4949
<label for="jwks-url">Issuer, Discovery Document or JWKs URI</label>
50-
<input type="url" class="form-control mb-2 mr-sm-2" id="jwks-url" name="jwks-url" aria-describedby="jwks-url-help" data-pristine="true" />
50+
<input type="url" class="form-control mb-2 mr-sm-2 bg-dark text-light" id="jwks-url" name="jwks-url" aria-describedby="jwks-url-help" data-pristine="true" />
5151
<small id="jwks-url-help" class="form-text text-muted">
5252
Optionally, you can provide the issuer, discovery document or JWKs URI to validate the JWT's signature.
5353
If you leave this field empty, the tool will use the value of the 'iss' claim.
@@ -114,7 +114,7 @@
114114
case 'kid':
115115
return '// Key ID, identifying which key was used to sign the JWT';
116116
case 'typ':
117-
return '// Type of the token, typically "JWT"';
117+
return `// Token type: ${explainTokenType(value)}`;
118118
case 'cty':
119119
return '// Content type, similar to MIME type, indicating the media type of the JWT.';
120120
case 'jwk':
@@ -233,6 +233,26 @@
233233
}
234234
}
235235
236+
function explainTokenType(type) {
237+
switch (type.toLowerCase()) {
238+
case 'jwt':
239+
case 'http://openid.net/specs/jwt/1.0':
240+
return 'JSON Web Token';
241+
case 'jws':
242+
return 'JSON Web Signature, a signed JWT';
243+
case 'jwe':
244+
return 'JSON Web Encryption, an encrypted JWT';
245+
case 'at+jwt':
246+
return 'Access Token';
247+
case 'token-introspection+jwt':
248+
return 'JWT response from the Introspection endpoint';
249+
case 'secevent+jwt':
250+
return 'Security Event Token';
251+
default:
252+
return type;
253+
}
254+
}
255+
236256
function convertEpoch(epoch) {
237257
if (typeof epoch === 'number') {
238258
const date = new Date(epoch * 1000);
@@ -307,7 +327,8 @@
307327
}
308328
});
309329
310-
$('#jwt-input').on('input', async function() {
330+
const jwtInput = $('#jwt-input');
331+
jwtInput.on('input', async function() {
311332
decodedJwt = {
312333
header: null,
313334
payload: null,
@@ -341,7 +362,14 @@
341362
}
342363
});
343364
344-
await showDecodedJwt(null, null, null, ' ');
365+
@if (Model.View?.Token != null)
366+
{
367+
@:jwtInput.trigger('input'); // Trigger input event to decode the JWT if it was provided
368+
}
369+
else
370+
{
371+
@:await showDecodedJwt(null, null, null, ' ');
372+
}
345373
}
346374
347375
function colorJwtInput(target, parts) {
Lines changed: 55 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,62 @@
1-
using Microsoft.AspNetCore.Authorization;
1+
using IdentityModel.Client;
2+
using Microsoft.AspNetCore.Authorization;
3+
using Microsoft.AspNetCore.Mvc;
24
using Microsoft.AspNetCore.Mvc.RazorPages;
5+
using Microsoft.Extensions.Caching.Memory;
36

47
namespace IdentityServerHost.Pages.Home;
58

69
[AllowAnonymous]
7-
public class JwtDecoder : PageModel
10+
public class JwtDecoder(IHttpClientFactory clientFactory, IMemoryCache cache) : PageModel
811
{
12+
private const string CacheKey = "jwt_decoder::access_token";
13+
14+
public ViewModel View { get; set; } = default!;
15+
16+
public async Task<IActionResult> OnGet()
17+
{
18+
var token = await GetOrCreateToken();
19+
View = new ViewModel { Token = token };
20+
21+
return Page();
22+
}
23+
24+
private async Task<string> GetOrCreateToken()
25+
{
26+
try
27+
{
28+
if (cache.TryGetValue(CacheKey, out string cachedToken))
29+
{
30+
return cachedToken;
31+
}
32+
33+
var request = HttpContext.Request;
34+
var authority = request.Scheme + "://" + request.Host.ToUriComponent();
35+
36+
var client = clientFactory.CreateClient();
37+
var response = await client.RequestClientCredentialsTokenAsync(new ClientCredentialsTokenRequest
38+
{
39+
Address = $"{authority}/connect/token",
40+
41+
ClientId = "m2m",
42+
ClientSecret = "secret",
43+
44+
Scope = "api"
45+
});
46+
47+
if (!response.IsError)
48+
{
49+
var token = response.AccessToken;
50+
cache.Set(CacheKey, token, TimeSpan.FromMinutes(20));
51+
52+
return token;
53+
}
54+
}
55+
catch
56+
{
57+
return null;
58+
}
59+
60+
return null;
61+
}
962
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
namespace IdentityServerHost.Pages.Home;
2+
3+
public class ViewModel
4+
{
5+
public string Token { get; set; }
6+
}

0 commit comments

Comments
 (0)