| Crates.io | supabase-jwt |
| lib.rs | supabase-jwt |
| version | 0.1.1 |
| created_at | 2025-08-09 23:32:47.589999+00 |
| updated_at | 2025-08-10 11:29:43.746488+00 |
| description | A lightweight, framework-agnostic library for validating Supabase Auth JWTs using a cached JWKS. |
| homepage | https://github.com/OpenMindOpenWorld/supabase-jwt |
| repository | https://github.com/OpenMindOpenWorld/supabase-jwt |
| max_upload_size | |
| id | 1788323 |
| size | 273,299 |
English | 简体中文
A lightweight, framework-agnostic Rust library for validating Supabase Auth JWTs, with JWKS caching support.
tokio.use supabase_jwt::{Claims, JwksCache};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// 1. Initialize the JWKS cache
// Get the URL from your Supabase project's API settings
let jwks_url = "https://<your-project-ref>.supabase.co/auth/v1/jwks";
let jwks_cache = JwksCache::new(jwks_url);
// 2. Get the Bearer Token from the request
let bearer_token = "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."; // Get from Authorization header
// 3. Validate the JWT and extract claims
// from_bearer_token automatically handles the "Bearer " prefix
match Claims::from_bearer_token(bearer_token, &jwks_cache).await {
Ok(claims) => {
// 4. Access user information
println!("User ID: {}", claims.user_id());
println!("Email: {:?}", claims.email());
println!("Role: {}", claims.role());
println!("Issued at: {}", claims.issued_at());
println!("Expires at: {}", claims.expires_at());
}
Err(e) => {
eprintln!("Token validation failed: {:?}", e);
}
}
Ok(())
}
Add the following to your Cargo.toml:
[dependencies]
supabase-jwt = "0.1.0" # Please check for the latest version on crates.io
tokio = { version = "1.47.0", features = ["full"] }
It's recommended to use axum-extra to elegantly extract the Bearer Token.
use axum::{
extract::State,
http::StatusCode,
response::{IntoResponse, Json},
routing::get,
Router,
};
use axum_extra::headers::{authorization::Bearer, Authorization};
use axum_extra::TypedHeader;
use supabase_jwt::{Claims, JwksCache};
use std::sync::Arc;
async fn protected_handler(
State(jwks_cache): State<Arc<JwksCache>>,
TypedHeader(Authorization(bearer)): TypedHeader<Authorization<Bearer>>,
) -> Result<Json<serde_json::Value>, StatusCode> {
let claims = Claims::from_token(bearer.token(), &jwks_cache)
.await
.map_err(|_| StatusCode::UNAUTHORIZED)?;
Ok(Json(serde_json::json!({
"user_id": claims.user_id(),
"email": claims.email()
})))
}
/*
// Set the state in your application
async fn run_app() {
let jwks_cache = Arc::new(JwksCache::new("..."));
let app = Router::new()
.route("/protected", get(protected_handler))
.with_state(jwks_cache);
// Start the server...
}
*/
In Actix Web, you can manually extract the token from the request headers.
use actix_web::{web, HttpRequest, HttpResponse, Result};
use supabase_jwt::{Claims, JwksCache};
async fn protected_handler(
req: HttpRequest,
jwks_cache: web::Data<JwksCache>,
) -> Result<HttpResponse> {
let bearer_token = req
.headers()
.get("Authorization")
.and_then(|h| h.to_str().ok())
.ok_or_else(|| actix_web::error::ErrorUnauthorized("Missing authorization header"))?;
let claims = Claims::from_bearer_token(bearer_token, &jwks_cache)
.await
.map_err(|e| {
eprintln!("Token validation error: {:?}", e);
actix_web::error::ErrorUnauthorized("Invalid token")
})?;
Ok(HttpResponse::Ok().json(serde_json::json!({
"user_id": claims.user_id(),
"role": claims.role()
})))
}
The Claims struct provides convenient methods to access standard and custom information in the JWT.
// Basic information
let user_id = claims.user_id(); // User ID (sub)
let email = claims.email(); // Email (email)
let role = claims.role(); // Role (role)
let phone = claims.phone(); // Phone number (phone)
let is_anon = claims.is_anonymous(); // Whether the user is anonymous (is_anonymous)
// Timestamps
let issued_at = claims.issued_at(); // Issued at (iat)
let expires_at = claims.expires_at(); // Expires at (exp)
// Metadata
// Assuming user_metadata = {"custom_field": "value"}
let custom_field: Option<String> = claims.get_user_metadata("custom_field");
// Assuming app_metadata = {"feature_enabled": true}
let app_setting: Option<bool> = claims.get_app_metadata("feature_enabled");
JwksCache provides a smart key caching mechanism to efficiently validate tokens.
let jwks_cache = JwksCache::new("https://<project>.supabase.co/auth/v1/jwks");
// Automatically fetch JWKS from cache or network
let jwks = jwks_cache.get_jwks().await?;
// Find a specific key (usually called internally by from_token)
let key = jwks_cache.find_key("key_id").await?;
For more details, please refer to the full API documentation on docs.rs.
It's good practice to handle different authentication errors gracefully. from_bearer_token returns a detailed AuthError enum.
use supabase_jwt::{AuthError, Claims, JwksCache};
async fn handle_request(bearer_token: &str, jwks_cache: &JwksCache) {
match Claims::from_bearer_token(bearer_token, jwks_cache).await {
Ok(claims) => {
println!("Successfully validated token for user: {}", claims.user_id());
}
Err(e) => {
eprintln!("Authentication failed: {}", e);
// Example of handling specific errors
match e {
AuthError::InvalidToken => {
// Trigger re-authentication
}
AuthError::Verification => {
// Token signature is invalid, might be a security risk
}
AuthError::JwksError(_) => {
// Could be a network issue or Supabase outage
}
_ => {
// Handle other cases
}
}
}
}
}
JwksCache is designed for high availability and performance with a built-in smart caching strategy, requiring no manual configuration:
Normal Cache: JWKS are cached for 24 hours.
Fallback Cache: If fetching new keys fails (e.g., due to a network error), the cache will continue to serve the last known valid keys for up to 7 days. This prevents your application from failing if the Supabase Auth service is temporarily unavailable.
Network Timeout: All network requests to the JWKS endpoint have a 5-second timeout to prevent your application from hanging.
This library is based on the design philosophy of "Trust Supabase Auth, focus on parsing stability":
We take code quality and reliability very seriously and ensure it through a comprehensive testing strategy.
cargo-tarpaulin.wiremock to simulate the Supabase Auth API, ensuring test stability and independence.You can run the tests with the following commands:
# Run all tests
cargo test
# Calculate code coverage
cargo tarpaulin --include-tests
Issues and Pull Requests are welcome! Before contributing, please ensure:
cargo fmt to format the code.cargo clippy to check code quality.cargo test to ensure all tests pass.This project is dual-licensed under MIT or Apache-2.0. See LICENSE-MIT and LICENSE-APACHE for details.