# JwtVerify()

Verifies a JSON Web Token (JWT) signature and returns the claims (the data payload).

Also validates expiration, not-before, issuer, and audience if specified.

**Requires Extension:** [Crypto Extension](https://download.lucee.org/#17AB52DE-B300-A94B-E058FC978BE4542D)

```
JwtVerify( token=string, key=any, algorithms=any, issuer=string, audience=string, clockSkew=numeric, throwOnError=boolean );
```

**Returns:** any

# Arguments

| Argument | Type | Required | Description | Default |
|----------|------|----------|-------------|---------|
| token | string | Yes | JWT string to verify. |  |
| key | any | Yes | Verification key. For HMAC: string or byte array. For RSA/EC: PEM string or Java PublicKey object. |  |
| algorithms | any | No | Allowed algorithms. Array or comma-separated string. Security best practice to restrict algorithms. |  |
| issuer | string | No | Required issuer (iss claim). Validation fails if token issuer doesn't match. |  |
| audience | string | No | Required audience (aud claim). Validation fails if audience is not in token's audience list. |  |
| clockSkew | numeric | No | Clock skew tolerance in seconds for exp/nbf validation. Default 0. |  |
| throwOnError | boolean | No | If true (default), throws on invalid token. If false, returns struct with valid, claims, error. Note: defaults to true unlike hash verify functions, since JWT validation failures typically indicate a security issue. | true |

# Usage Notes

**Always restrict algorithms in production.** Without the `algorithms` parameter, any algorithm is accepted. This can enable algorithm confusion attacks where an attacker switches from RS256 to HS256 and signs with the public key. Always specify which algorithms you expect:

```cfml
claims = JwtVerify( token = token, key = secret, algorithms = "HS256" );
```

**throwOnError behaviour:** By default, invalid tokens throw an exception. Pass `throwOnError=false` to get a result struct instead — useful for login flows where you want to handle errors gracefully without try/catch:

- Valid: `{ valid: true, claims: { sub: "user123", ... } }`
- Invalid: `{ valid: false, error: "Token has expired" }`

**Clock skew:** Distributed systems often have small clock differences. Use `clockSkew` (in seconds) to add tolerance when checking `exp` and `nbf` claims. A value of 30–60 seconds is typical.

**Issuer and audience validation:** Always validate these in production to ensure the token was issued by the expected provider and intended for your application.

# Examples

```cfml
// Verify an HMAC-signed token - returns the claims struct on success
secret = "my-super-secret-key-that-is-at-least-256-bits-long";
token = JwtSign( { sub: "user123", role: "admin" }, secret, "HS256" );

claims = JwtVerify( token = token, key = secret );
// claims.sub == "user123", claims.role == "admin"

// Verify an RSA-signed token with the public key
keyPair = GenerateKeyPair( "RSA-2048" );
token = JwtSign( claims = { sub: "user123" }, key = keyPair.private, algorithm = "RS256" );
claims = JwtVerify( token = token, key = keyPair.public );

// By default, invalid tokens throw an exception. Use throwOnError=false to get
// a result struct instead - useful for login flows where you want to handle errors gracefully
result = JwtVerify( token = token, key = "wrong-key", throwOnError = false );
// result.valid == false, result.error contains the error message

result = JwtVerify( token = token, key = keyPair.public, throwOnError = false );
// result.valid == true, result.claims contains the decoded claims

// Validate issuer and audience - throws if they don't match
token = JwtSign(
	claims = { sub: "user123" },
	key = secret,
	algorithm = "HS256",
	issuer = "https://myapp.com",
	audience = "api"
);
claims = JwtVerify(
	token = token,
	key = secret,
	issuer = "https://myapp.com",
	audience = "api"
);

// Allow clock skew for expiration checks (useful for distributed systems)
// This allows tokens up to 60 seconds past their expiration
claims = JwtVerify( token = token, key = secret, clockSkew = 60 );

// Restrict which algorithms are accepted (security best practice)
claims = JwtVerify( token = token, key = secret, algorithms = [ "HS256", "HS384" ] );
// or as a comma-separated string
claims = JwtVerify( token = token, key = secret, algorithms = "HS256,HS384" );

// Positional arguments: token, key
claims = JwtVerify( token, secret );
```







# Categories

[Cryptography](../../categories/crypto.md)

# See Also

[JwksLoad()](jwksload.md), [JwtDecode()](jwtdecode.md), [JwtSign()](jwtsign.md)