JWT Verify
Rust library for verifying JWTs signed by Amazon Cognito, and any OIDC-compatible IDP.
Inspired by awslabs/aws-jwt-verify.
Features
- Comprehensive validation of JWT tokens (ID tokens and Access tokens)
- Support for both AWS Cognito and generic OIDC providers
- Efficient JWK key management with automatic caching
- Multiple user pools/providers with automatic issuer matching
- Multiple client IDs per pool/provider
- Configurable clock skew and cache duration
- JWK prefetching (hydration) for cold start optimization
- Detailed error handling and reporting
- Thread-safe for use in async contexts
- Example Axum web servers demonstrating integration patterns
Installation
Add this to your Cargo.toml:
[]
= "0.1.2"
Quick Start
Verifying AWS Cognito Tokens
use ;
async
Need provider-specific fields? The examples above use trait methods that work across all providers. To access Cognito-specific fields like cognito_groups, cognito_username, or custom claims, see the Downcasting section.
Verifying OIDC Tokens
use ;
async
Need provider-specific fields? The example above uses trait methods that work across all providers. To access OIDC-specific fields like preferred_username, picture, locale, or custom claims, see the Downcasting section.
Advanced Usage
Single Pool with Multiple Client IDs
A common use case is having one user pool with multiple client IDs (e.g., web app, mobile app):
use ;
use Duration;
async
Multiple User Pools
The verifier automatically selects the correct user pool based on the token's issuer claim:
use ;
async
JWK Prefetching (Hydration)
Prefetch JWKs to avoid cold start latency:
use ;
async
Multiple OIDC Providers
use ;
async
Examples
The library includes comprehensive examples demonstrating various use cases:
Basic Examples (CLI)
cognito_basic.rs: AWS Cognito JWT verification including:- Single user pool with single client ID
- Multiple user pools with different client IDs
- Single user pool with multiple client IDs (web/mobile apps)
- Negative test cases (wrong token types, expired tokens, etc.)
oidc_basic.rs: OIDC JWT verification including:- Single provider with single client ID
- Multiple providers with different client IDs
- Single provider with multiple client IDs
- Negative test cases
Axum Integration Examples (Web Server)
cognito_axum.rs: Full-featured Axum web server with Cognito JWT authentication:- Public and protected endpoints
- ID token and access token verification
- Role-based access control (scope checking)
- Proper HTTP error responses
- JWK prefetching on startup
oidc_axum.rs: Same features ascognito_axum.rsbut for OIDC providers
Running Examples
-
Set up configuration using a
.envfile:# Edit .env with your actual configuration -
Run the examples:
# Basic CLI examples # Axum web server examples -
Test the Axum server endpoints:
# Public endpoint (no auth) # Protected endpoint with ID token # Protected endpoint with access token # Admin endpoint (requires 'admin' scope)
Example Configuration
The examples support various configurations through environment variables:
# Single user pool with multiple client IDs
AWS_REGION=us-east-1
COGNITO_USER_POOL_ID=us-east-1_example
COGNITO_CLIENT_ID=web-app-client-id
COGNITO_CLIENT_ID_2=mobile-app-client-id
# Your test tokens
COGNITO_ID_TOKEN=your-id-token
COGNITO_ACCESS_TOKEN=your-access-token
See examples/README.md for detailed configuration instructions and more examples.
Common Use Cases
ID Token vs Access Token
- ID Tokens: Used for authentication - contains user identity information (email, name, etc.)
- Access Tokens: Used for authorization - contains scopes and permissions
// Verify ID token for authentication
let id_claims = verifier.verify_id_token.await?;
println!;
// Verify access token for authorization
let access_claims = verifier.verify_access_token.await?;
if access_claims.has_scope
Downcasting to Access Provider-Specific Fields
The verify_id_token() and verify_access_token() methods return trait objects (Box<dyn IdTokenClaims> and Box<dyn AccessTokenClaims>). While trait methods provide access to common fields, you can downcast to concrete types to access provider-specific fields.
Why Downcast?
- Trait methods provide access to standard fields (sub, email, scopes, etc.)
- Downcasting gives you access to provider-specific fields (Cognito groups, OIDC picture, custom claims, etc.)
Cognito Example
use ;
async
OIDC Example
use ;
async
Important: Match Token Type to Claims Type
Always downcast to the correct type that matches your token:
| Token Type | Verifier Method | Downcast To |
|---|---|---|
| Cognito ID Token | verify_id_token() |
CognitoIdTokenClaims |
| Cognito Access Token | verify_access_token() |
CognitoAccessTokenClaims |
| OIDC ID Token | verify_id_token() |
OidcIdTokenClaims |
| OIDC Access Token | verify_access_token() |
OidcAccessTokenClaims |
Common Mistake:
// ❌ WRONG: Trying to downcast ID token to AccessTokenClaims
let claims = verifier.verify_id_token.await?;
let wrong = claims.; // Won't compile!
// ✅ CORRECT: Downcast ID token to IdTokenClaims
let claims = verifier.verify_id_token.await?;
let correct = claims.; // Works!
When to Use Downcasting
| Approach | Use When |
|---|---|
| Trait methods only | You only need standard fields (sub, email, scopes, exp, etc.) |
| Downcasting | You need provider-specific fields (groups, custom claims, etc.) |
| Concrete verifier type | You only use one provider and want to avoid trait objects entirely |
Alternative: Use Concrete Verifier Type
If you only use one provider and want to avoid trait objects, use the concrete verifier type directly:
use ;
let verifier = new_single_pool?;
// Use the generic verify method directly (no trait object)
let claims: CognitoAccessTokenClaims = verifier.verify.await?;
// Direct access to all fields, no downcasting needed
println!;
Error Handling
The library provides detailed error information for debugging:
match verifier.verify_id_token.await
Best Practices
- Reuse verifier instances: Create a single verifier instance and reuse it for all verifications (thread-safe)
- Set appropriate clock skew: Use 1-2 minutes to account for time differences between systems
- Configure cache duration: Match your IdP's key rotation policy (default: 12 hours)
- Prefetch JWKs: Use
hydrate()to warm up the cache and avoid cold start latency - Use correct token types: ID tokens for authentication, access tokens for authorization
- Validate scopes: Always check scopes in access tokens for authorization decisions
- Handle errors gracefully: Don't expose detailed error information to clients in production
- Multiple client IDs: Use a single pool/provider config with multiple client IDs for different apps (web, mobile)
License
This project is licensed under the MIT License.