Verify every JWT with a maintained library against an explicit algorithm allowlist, keys from the issuer’s configured JWKS only, and the expected issuer, audience, token type and expiry. Never accept alg: none, never let a header choose the key (jku, x5u, jwk) or turn a public key into an HMAC secret, and treat valid claims as authenticated, not authorized. Publish a new key before signing with it; remove a compromised key at once.
- Decoding is not verification: reject the token before using claims if the algorithm, key or signature fails.
- Pin the algorithms per issuer, for example
["RS256"], and bind each key to one algorithm and key type. - Load keys only from configured trust; treat
kidas a lookup into that set, never as a path, query or URL. - Check
iss,aud,exp,nbfand the token type, such asat+jwtfor RFC 9068 access tokens, with a small clock tolerance. HS256secrets need at least 256 random bits; JWT payloads are readable, so keep secrets and unnecessary personal data out of claims.- Planned rollover publishes before use and retires after the last token expires; a compromised key is removed now.
| Setting | Value | Note | Source |
|---|---|---|---|
| Algorithms | One explicit list per issuer, such as RS256, ES256 or Ed25519 | Never none; never mix HMAC and public-key algorithms for one key. | RFC 8725 section 3.1; ASVS 5.0 V9.1.2 |
| Key source | The issuer’s configured JWKS URL | Ignore jku, x5u, jwk and x5c from the token. | RFC 8725 section 3.10; ASVS 5.0 V9.1.3 |
| JWKS refresh | Unknown kid: at most one fetch per 30 seconds; cache up to 10 minutes | These are the jose createRemoteJWKSet defaults. | jose documentation |
HMAC secret (HS256) | At least 256 random bits | Longer for HS384 and HS512; prefer asymmetric keys across services. | RFC 7518 section 3.2 |
| Access token type and claims | typ of at+jwt; iss, exp, aud, sub, client_id, iat and jti present | Reject ID tokens and other JWT kinds at the API. | RFC 9068 sections 2.1 and 2.2 |
| Clock tolerance | 60 seconds or less | Spring Security's default is 60 seconds; the Node.js and .NET examples use 30. | This guide |
- Send a token with
alg: noneand one signedHS256with the public key as the secret; both must return 401. - Send an otherwise valid token with a
jkuorjwkheader pointing at your own key; it must fail and trigger no outbound fetch. - Send an ID token, a token for another audience and an expired token; each must fail with a distinct internal reason code.
- Send ten tokens with unknown
kidvalues in a second and confirm at most one JWKS fetch. - Rehearse a rollover: publish K2, wait out the cache bound, sign with K2, then confirm K1 tokens fail after the cutoff.
- Applies to
- jose (Node.js) 6.x (tested with 6.2.12)
- PyJWT 2.x (tested with 2.15.0)
Signed JWT validation and signing-key rollover. Claim rules and key-fetch behavior must match the protocol and issuer.
- Part of
- Learning path: Identity through the whole lifecycle (step 2 of 14). Workflows: Fix a finding, Secure a feature, Test an application
Tested fixes: the vulnerable code, the fix, a plausible fix that still fails, and the regression tests that tell them apart.
Learning goals and prerequisites
What you will learn
- Define a JWT validation profile before parsing claims.
- Recognize and block the common JWT attacks by name.
- Sequence planned versus emergency signing-key rollover.
- Distinguish real JOSE verification from authorization policy.
Before you start
API developers who accept JWTs, and reviewers who check token validation and signing-key operations.
- Read issuer, audience, expiration, key ID and the difference between decoding and verification.
Refresh first: OAuth 2.0 and OpenID Connect login: authorization code with PKCE
Go to the guided practiceA compact signed JWT has three base64url parts: protected header, payload, and signature. Decoding the first two reveals attacker-controlled data before verification, and anyone holding the token can read them: signing does not make claims confidential. A valid signature establishes that the signed bytes were produced by a holder of a particular key; it does not by itself establish that this token belongs to your issuer, audience, token type, time window, or authorization decision.
Choose a validation profile before reading claims: expected issuer, allowed algorithm set, trusted key source, expected audience, required claims, token purpose, and time policy. The header can select a candidate key by kid within an already trusted key set. It must not choose an arbitrary network location or turn an asymmetric verification key into a symmetric secret.
For an invoices API, a validated token might assert iss=https://issuer.example, aud=invoices-api, sub=alice, tenant=acme, and typ=at+jwt under the chosen profile. Authorization still has to compare alice and acme with the trusted invoice record. A signed tenant claim does not mean every invoice in that tenant is readable.
Each attack below has been exploited in real libraries or services. A verifier configured as in this guide rejects all of them; the tested examples further down include tokens for the first three. The JWT attacks guide at /domains/identity-crypto/authentication/jwt-attacks/ explains each one.
| Attack | What goes wrong | Defense |
|---|---|---|
alg: none | An unsecured token, which RFC 7519 allows, is accepted with no signature. | Pass an explicit algorithm list; jose and PyJWT then reject none. |
Algorithm confusion (RS256 to HS256) | The verifier uses the RSA public key, which anyone can read, as an HMAC secret. | Bind each key to one algorithm and key type; never take the algorithm from the header. |
Header-supplied keys (jwk, jku, x5u, x5c) | The token carries or points to the attacker’s key, or makes the server fetch an attacker URL. | Ignore key material in headers; load keys only from the configured JWKS URL. |
kid injection | A kid used as a file path or database query enables traversal, injection or an empty key. | Look up kid by exact match in the key set you already loaded. |
| Weak HMAC secret | One captured token lets an attacker guess a short secret offline and then mint tokens. | Use random secrets of at least 256 bits for HS256, or asymmetric keys. |
| Cross-JWT confusion | An ID token, or a token issued for another API, is accepted as an access token. | Check typ, aud and iss exactly, with separate rules per token kind. |
| Keys trusted beyond their issuer | In 2023 a consumer Microsoft account signing key was accepted for enterprise Azure AD tokens because of a validation issue (Storm-0558, /domains/identity-crypto/authentication/case-study-storm-0558/). | Bind each key to the one issuer that publishes it, and reject a token whose iss does not own the key. |
| Signature-check bugs | Java 15 to 18 before the April 2022 updates accepted all-zero ECDSA signatures (CVE-2022-21449). | Patch runtimes and test with invalid signatures, not only valid ones. |
| Newer cases (RFC 8725 update draft) | A case-variant alg such as noNE slips past a blocklist; JWE decompression bombs; huge PBES2 p2c counts; JSON-serialized JWS read as compact. | Use allowlists, cap decompression and iteration counts, and accept only the compact form. |
Use a maintained JOSE implementation with explicit algorithm and claim validation. Reject malformed encodings, unsupported algorithms, missing required fields, ambiguous key selection, and invalid signatures before using the payload as authenticated identity. Enforce exact issuer matching and the profile’s audience semantics; a substring match is not an audience check.
Apply a defined clock-skew allowance to time validation, and bound token lifetime where the application requires it. Avoid a blanket “ignore expiration during outages” fallback. The verifier’s unavailable key source is an availability problem, not evidence that an unverified token is acceptable.
Unknown kid values can trigger bounded refresh of the configured JWKS. Without caching, cooldowns, and limits, attackers can force a network fetch for every invented key ID. A token-supplied jku or x5u must not turn verification into arbitrary URL fetching. Key discovery belongs to trusted issuer configuration.
The downloadable “Secure a real HTTP API” project performs actual JOSE signing and verification in its tests. The learner edits authorization and profile-field selection in exercise.mjs, not the verifier. This is a useful boundary demonstration: bad signatures and audiences fail before the learner’s authorization function runs; same-tenant ownership errors occur afterward.
This verifier pins the algorithm, issuer, audience and RFC 9068 token type, and takes keys only from the issuer’s JWKS with a refresh cooldown. It was tested with jose 6.2.12 on Node.js 26 against a local JWKS: valid tokens pass, and alg: none, an HS256 token signed with the RSA public key, attacker keys in jwk or jku, wrong audience, issuer or type, and expired tokens all fail.
import {createRemoteJWKSet, jwtVerify} from 'jose';
// Keys come only from the issuer's configured JWKS URL, never from token headers
// (jku, x5u, jwk). Unknown kid values refresh the set at most once every 30 seconds.
const jwks = createRemoteJWKSet(new URL('https://issuer.example/.well-known/jwks.json'), {
cooldownDuration: 30_000,
cacheMaxAge: 600_000,
});
export async function verifyAccessToken(token) {
const {payload} = await jwtVerify(token, jwks, {
algorithms: ['RS256'], // exact allowlist: rejects "none", HS256 and anything else
issuer: 'https://issuer.example',
audience: 'invoices-api',
typ: 'at+jwt', // RFC 9068 access tokens, so an ID token is refused
requiredClaims: ['sub', 'iat'],
maxTokenAge: '15m',
clockTolerance: 30,
});
return payload; // authenticated claims; authorization still happens per request
}PyJWT checks the signature, algorithm, issuer, audience and required claims in jwt.decode; the token type is checked afterwards because the header is covered by the signature. Tested with PyJWT 2.15.0 against the same attack tokens as the Node.js example.
import jwt # PyJWT 2.x
# Keys come only from the issuer's JWKS URL; get_signing_key_from_jwt matches the kid there.
jwks = jwt.PyJWKClient("https://issuer.example/.well-known/jwks.json", cache_keys=True, lifespan=600)
def verify_access_token(token: str) -> dict:
key = jwks.get_signing_key_from_jwt(token)
claims = jwt.decode(
token,
key.key,
algorithms=["RS256"], # never take this list from the token header
audience="invoices-api",
issuer="https://issuer.example",
leeway=30,
options={"require": ["exp", "iat", "iss", "aud", "sub"]},
)
typ = str(jwt.get_unverified_header(token).get("typ", "")).lower().removeprefix("application/")
if typ != "at+jwt": # RFC 9068 access token; the header is covered by the verified signature
raise jwt.InvalidTokenError("not an access token")
return claimsSpring Security's resource server checks every bearer token with the JwtDecoder bean. createAtJwtValidator() (Spring Security 6.5 and later) enforces the RFC 9068 profile: typ of at+jwt, the required claims, and the exact issuer and audience. The decoder's own type check is off by default since Spring Security 7.0; validateType(false) keeps it off on 6.5, where it would reject at+jwt. The method is validateType even though Spring's reference documentation shows validateTypes. Tested with Spring Boot 4.1.1 (Spring Security 7.1.1, Nimbus JOSE + JWT 10.9.1) on JDK 21 against a local JWKS: a valid token passes, and alg: none, an HS256 token signed with the RSA public key, an ID-token type, a wrong audience or issuer, an expired token, a missing jti, and attacker keys in jku or jwk all fail, with no request to the jku URL.
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.jwt.JwtValidators;
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
class ResourceServerConfig {
@Bean
SecurityFilterChain api(HttpSecurity http) throws Exception {
http.authorizeHttpRequests(requests -> requests.anyRequest().authenticated())
.oauth2ResourceServer(server -> server.jwt(Customizer.withDefaults())); // uses the bean below
return http.build();
}
@Bean
JwtDecoder jwtDecoder() {
// Keys come only from the issuer's JWKS URL; the decoder ignores jku, x5u and jwk headers.
NimbusJwtDecoder decoder = NimbusJwtDecoder
.withJwkSetUri("https://issuer.example/.well-known/jwks.json")
.jwsAlgorithm(SignatureAlgorithm.RS256) // exact allowlist: rejects none, HS256, ES256
.validateType(false) // the RFC 9068 validator below checks typ instead
.build();
// RFC 9068: typ at+jwt; exp, iat, sub, jti and client_id present; exact iss and aud;
// 60 seconds of clock skew by default.
decoder.setJwtValidator(JwtValidators.createAtJwtValidator()
.issuer("https://issuer.example")
.audience("invoices-api")
.build());
return decoder;
}
}AddJwtBearer loads the issuer's metadata and signing keys from Authority and validates each token with TokenValidationParameters. ValidAlgorithms and ValidTypes are the two settings most often missing: without them any algorithm the key supports, and any token type, are accepted. The default clock skew is 5 minutes, so set it explicitly. These parameters were tested with Microsoft.AspNetCore.Authentication.JwtBearer 10.0.12 (Microsoft.IdentityModel 8.19.2) on .NET 10: a valid at+jwt token passes, and a typ of JWT, a wrong audience or issuer, an expired token, alg: none, HS256 with the public key, attacker keys in jwk or jku and an ES256 token all fail.
// Program.cs, ASP.NET Core 10 with Microsoft.AspNetCore.Authentication.JwtBearer
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
// Signing keys come from the issuer's discovery document and JWKS, never from the token.
options.Authority = "https://issuer.example";
options.MapInboundClaims = false; // keep "sub" and "client_id" as issued
options.TokenValidationParameters = new TokenValidationParameters
{
ValidIssuer = "https://issuer.example",
ValidAudience = "invoices-api",
ValidAlgorithms = [SecurityAlgorithms.RsaSha256], // exact allowlist: no "none", no HS256
ValidTypes = ["at+jwt"], // RFC 9068 access tokens only, so an ID token is refused
ClockSkew = TimeSpan.FromSeconds(30), // the default is 5 minutes
RequireExpirationTime = true,
RequireSignedTokens = true,
};
});
builder.Services.AddAuthorization();
var app = builder.Build();
app.UseAuthentication();
app.UseAuthorization();
app.MapGet("/invoices", () => Results.Ok()).RequireAuthorization();
app.Run();For planned rollover, publish new public key K2 alongside K1, allow verifiers to refresh, then begin issuing with K2. Continue accepting K1 only for the period when legitimately issued K1 tokens can remain valid. After their maximum expiry plus the configured clock-skew allowance, retire K1 from normal validation and ensure caches converge.
For example, if the last K1 token was issued at 10:00 with a ten-minute lifetime and a thirty-second skew allowance, the normal overlap must account for acceptance until just before 10:10:30 under an exclusive cutoff rule. The publication lead time depends on cache behavior, so write it down rather than assuming immediate propagation.
Compromise rollover is different: an attacker with K1 can issue fresh-looking tokens. Waiting for an ordinary expiry window may preserve the attacker’s access. Remove or deny K1 promptly, revoke affected sessions or grants where possible, and accept the controlled disruption required by the incident. Merely changing the signer while every verifier still trusts K1 is incomplete. JWTs stay valid until they expire, so keep access-token lifetimes short and keep server-side state, such as session versions or a deny list keyed by jti, for revocation before expiry.
A valid K1 token before cutoff and valid K2 token after propagation should pass. Wrong issuer, audience, purpose, signature, and expired tokens must fail even when their kid is known. Unknown kid refresh should be bounded, then fail if no authorized key matches.
Test stale caches and a JWKS outage using a defined policy for already cached trusted keys. Verify that a removed compromised key cannot persist beyond the stated revocation target. Record which key ID and policy version made the decision, but avoid logging whole bearer tokens.
Two local projects on this page run real JOSE verification. In the “Secure a real HTTP API” project, use a supported Node.js release (22 or later) and npm, run npm ci --ignore-scripts and then npm test. The tests start a loopback HTTP service, generate temporary signing keys, and issue real tokens; they exercise wrong audience, expired token, invalid signature, wrong token purpose, and missing credentials. The “Verify tokens through rotation and revocation” project adds 24 tests against a real local JWKS, including a cached key that outlives its removal from the key set.
Repair the policy functions as instructed by each project: owner and tenant must match trusted invoice fields, and profile updates must select the permitted displayName field. Keep the verifier tests intact. The JWT validation lab at /labs/token-validation/ practices the claim checks in the browser.
Guided practice
Your invoices API accepts RFC 9068 access tokens from https://issuer.example with audience invoices-api, RS256 only, keys from the JWKS (currently k1 and k2) and 30 seconds of clock skew, rejecting at now >= exp + 30. At 14:00:00 UTC on 10 October 2026 (epoch 1791640800) five tokens arrive. Decode them yourself, decide each one, then plan the k1 retirement. Signatures: assume T1, T2 and T4 verify with the key their kid names; T3 and T5 were forged.
Supplied scenario material
T1 eyJhbGciOiJSUzI1NiIsInR5cCI6ImF0K2p3dCIsImtpZCI6ImsyIn0.eyJpc3MiOiJodHRwczovL2lzc3Vlci5leGFtcGxlIiwiYXVkIjoiaW52b2ljZXMtYXBpIiwic3ViIjoiYWxpY2UiLCJjbGllbnRfaWQiOiJ3ZWIiLCJpYXQiOjE3OTE2NDA1MDAsImV4cCI6MTc5MTY0MTEwMCwianRpIjoiN2YzYSJ9.c2lnbmF0dXJl
T2 eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImsyIn0.eyJpc3MiOiJodHRwczovL2lzc3Vlci5leGFtcGxlIiwiYXVkIjoiY2xpZW50LTciLCJzdWIiOiJhbGljZSIsIm5vbmNlIjoibi0wUzZfV3pBMk1qIiwiaWF0IjoxNzkxNjQwNzQwLCJleHAiOjE3OTE2NDEzNDB9.c2lnbmF0dXJl
T3 eyJhbGciOiJIUzI1NiIsInR5cCI6ImF0K2p3dCIsImtpZCI6ImsyIn0.eyJpc3MiOiJodHRwczovL2lzc3Vlci5leGFtcGxlIiwiYXVkIjoiaW52b2ljZXMtYXBpIiwic3ViIjoiYWxpY2UiLCJjbGllbnRfaWQiOiJ3ZWIiLCJpYXQiOjE3OTE2NDA1MDAsImV4cCI6MTc5MTY0MTEwMCwianRpIjoiN2YzYSJ9.c2lnbmF0dXJl
T4 eyJhbGciOiJSUzI1NiIsInR5cCI6ImF0K2p3dCIsImtpZCI6ImsxIn0.eyJpc3MiOiJodHRwczovL2lzc3Vlci5leGFtcGxlIiwiYXVkIjoiaW52b2ljZXMtYXBpIiwic3ViIjoiYWxpY2UiLCJjbGllbnRfaWQiOiJ3ZWIiLCJpYXQiOjE3OTE2NDAxODAsImV4cCI6MTc5MTY0MDc4MCwianRpIjoiNmIyMSJ9.c2lnbmF0dXJl
T5 eyJhbGciOiJSUzI1NiIsInR5cCI6ImF0K2p3dCIsImtpZCI6ImV2aWwiLCJqa3UiOiJodHRwczovL2F0dGFja2VyLmV4YW1wbGUvandrcy5qc29uIn0.eyJpc3MiOiJodHRwczovL2lzc3Vlci5leGFtcGxlIiwiYXVkIjoiaW52b2ljZXMtYXBpIiwic3ViIjoiYWRtaW4iLCJjbGllbnRfaWQiOiJ3ZWIiLCJpYXQiOjE3OTE2NDA1MDAsImV4cCI6MTc5MTY0MTEwMCwianRpIjoiOWMwZCJ9.c2lnbmF0dXJl
Rollover so far: k2 published 13:40:00; the issuer switched signing from k1 to k2 at 13:50:00; access tokens live 10 minutes; verifiers cache the JWKS for at most 5 minutes.1. Decode the tokens
Decode the header and payload of each token, and convert
iatandexpto UTC times.Shell# Header and payload of one token: put the token in TOKEN first node -e 'for (const part of process.argv[1].split(".").slice(0, 2)) console.log(Buffer.from(part, "base64url").toString())' "$TOKEN" # An epoch time as UTC node -e 'console.log(new Date(1791640780 * 1000).toISOString())'Expected result and reasoning
T1:
RS256,typat+jwt,kidk2;issandaudcorrect,subalice,exp14:05:00. T2:RS256,typJWT,kidk2;audclient-7and a nonce, so it is an ID token for the client. T3:HS256withkidk2, otherwise T1's claims. T4:RS256,at+jwt,kidk1;iat13:49:40,exp13:59:40. T5:RS256,kidevil, ajkupointing atattacker.example,subadmin.2. Decide each token at 14:00:00
Apply the validation profile to T1 to T5 and name the first check each rejected token fails.
Decide each token at 14:00:00
Expected result and reasoning
T1 passes. T2 fails the type check (
JWT, notat+jwt) and the audience check (client-7). T3 fails the algorithm allowlist before any key is used, which is what stops the RS256-to-HS256 key-confusion attack. T4 passes:exp13:59:40 plus 30 seconds of skew means it is rejected only from 14:00:10, and k1 is still trusted. T5 fails because no key withkidevilis in the configured JWKS; thejkuheader is ignored and no request goes toattacker.example.3. Plan the planned retirement of k1
Compute the earliest time k1 can be removed from every verifier, and check the publication lead time.
Expected result and reasoning
The last k1 token can be issued just before 13:50:00 and expires just before 14:00:00; with 30 seconds of skew it is acceptable until just before 14:00:30, so remove k1 at 14:00:30 at the earliest and allow for the 5-minute JWKS cache when removing it from the published set. k2 was published 10 minutes before first use, more than the 5-minute cache, so no verifier saw a k2 token before it could fetch k2.
4. Change it to a compromise
At 14:00:00 you learn that k1 was stolen. Decide what happens to T4 and what to do.
Expected result and reasoning
Remove k1 from the published JWKS and from verifier caches now, and reject T4 even though it is inside its skew window, because anyone holding k1 can mint new tokens that look like T4. Revoke sessions or refresh tokens issued during the exposure and accept the short disruption.
5. Write the regression tests
Turn the five decisions into tests for your verifier.
Expected result and reasoning
One test per token with a real signature from test keys: T1-like passes; an ID token is refused;
HS256signed with the public key is refused; a k1 token passes untilexp+ 30 seconds and fails at that instant; a token with an unknownkidand ajkuheader fails with no outbound request (assert the mock server received nothing). The Java and Node.js examples on this page were tested this way.
The tokens carry placeholder signatures, so they decode but do not verify; the signature outcomes are stated. Use the code on this page, or the token projects, to generate real signed tokens for the tests.
Save a draft on this device to resume later. Automatic saving begins after you choose Save. Drafts are never sent by this application; use synthetic data.
No draft saved on this device yet.
Repair the code
JWT validation lab: Validate the purpose and audience of a token
Run a local project
Download and unzip a starter with failing tests and a step-by-step README, edit the named exercise file, and run the tests until they pass. The reference solution is a separate download, so you can attempt the repair before you see it.
Secure a real HTTP API
Run Express requests with real signed tokens. Repair invoice authorization and stop user-controlled properties from changing roles or tenants.
Needs: Internet access for npm ci, which downloads the pinned packages; the tests run on localhost only.
In the extracted folder, run:
npm ci --ignore-scripts
npm test14 regression tests · The starter intentionally fails some cases.
Opens in a dev container (VS Code Dev Containers or GitHub Codespaces) with a pinned image, and includes Semgrep rules with their own tests in semgrep/.
Part of the learning path Developer foundations
Localhost tests with synthetic identities and in-memory records; includes actual JOSE verification.
Verify tokens through rotation and revocation
Operate a real loopback JWKS endpoint and signed-token API. Repair issuer, audience, purpose and session checks, then test planned rotation, stale key caches and emergency revocation.
Needs: Internet access for npm ci, which downloads the pinned jose package; the tests run on localhost only.
In the extracted folder, run:
npm ci --ignore-scripts
npm test24 regression tests · The starter intentionally fails some cases.
Opens in a dev container (VS Code Dev Containers or GitHub Codespaces) with a pinned image, and includes Semgrep rules with their own tests in semgrep/.
Part of the learning path Identity through the whole lifecycle
Real JOSE signatures and HTTP key discovery; synthetic in-memory sessions and deterministic clock. No identity-provider login or distributed revocation service.
Sources & further reading
The standards, specifications and documentation behind the guidance above. Each is the evidence for a claim on this page; consult it for detail beyond the defaults and versions listed here.
- RFC 8725: JWT best current practices. February 2020 (BCP 225).
- IETF draft: JWT best current practices update (rfc8725bis). draft-ietf-oauth-rfc8725bis-10.
- RFC 9068: JWT profile for OAuth 2.0 access tokens. October 2021.
- RFC 7519: JWT claims. May 2015.
- RFC 7518: JSON Web Algorithms, HMAC key size. May 2015; updated by RFC 9864.
- RFC 7517: JSON Web Key. May 2015.
- OWASP JSON Web Token Cheat Sheet
- jose: jwtVerify options
- Neil Madden: psychic signatures in Java (CVE-2022-21449). 19 April 2022.
- Spring Security: OAuth 2.0 resource server JWT, RFC 9068 validation
- Microsoft: configure JWT bearer authentication in ASP.NET Core
Reference information
- SDLC phase
- Development
- Role
- Developers
- ASVS 5.0.0 requirements
- v5.0.0-9.1.1 · v5.0.0-9.1.2 · v5.0.0-9.1.3 · v5.0.0-9.2.1 · v5.0.0-9.2.2 · v5.0.0-9.2.3
- CAPEC attack patterns