{"id":51031070,"url":"https://github.com/kanata996/supabase-jwt","last_synced_at":"2026-06-22T00:31:01.287Z","repository":{"id":309111330,"uuid":"1035099084","full_name":"kanata996/supabase-jwt","owner":"kanata996","description":"A lightweight, framework-agnostic library for validating Supabase Auth JWTs using a cached JWKS.","archived":false,"fork":false,"pushed_at":"2025-08-11T18:08:30.000Z","size":116,"stargazers_count":1,"open_issues_count":1,"forks_count":2,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-18T22:10:12.829Z","etag":null,"topics":["authentication","jwks","jwt","supabase","validation"],"latest_commit_sha":null,"homepage":"https://crates.io/crates/supabase-jwt","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/kanata996.png","metadata":{"files":{"readme":"README-zh_CN.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE-APACHE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2025-08-09T16:49:26.000Z","updated_at":"2026-06-10T14:56:57.000Z","dependencies_parsed_at":"2025-08-09T23:44:21.379Z","dependency_job_id":null,"html_url":"https://github.com/kanata996/supabase-jwt","commit_stats":null,"previous_names":["openmindopenworld/supabase-jwt"],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/kanata996/supabase-jwt","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kanata996%2Fsupabase-jwt","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kanata996%2Fsupabase-jwt/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kanata996%2Fsupabase-jwt/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kanata996%2Fsupabase-jwt/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/kanata996","download_url":"https://codeload.github.com/kanata996/supabase-jwt/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kanata996%2Fsupabase-jwt/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34630753,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-21T02:00:05.568Z","response_time":54,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["authentication","jwks","jwt","supabase","validation"],"created_at":"2026-06-22T00:31:01.062Z","updated_at":"2026-06-22T00:31:01.279Z","avatar_url":"https://github.com/kanata996.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# supabase-jwt\n\n[![Crates.io](https://img.shields.io/crates/v/supabase-jwt.svg)](https://crates.io/crates/supabase-jwt)\n[![Docs.rs](https://docs.rs/supabase-jwt/badge.svg)](https://docs.rs/supabase-jwt)\n[![Build Status](https://img.shields.io/github/actions/workflow/status/supabase-community/supabase-jwt-rs/ci.yml?branch=main)](https://github.com/supabase-community/supabase-jwt-rs/actions/workflows/ci.yml)\n[![License](https://img.shields.io/crates/l/supabase-jwt.svg)](https://github.com/supabase-community/supabase-jwt-rs/blob/main/LICENSE-MIT)\n\n[English](README.md) | 简体中文\n\n一个轻量级、框架无关的 Rust 库，用于验证 Supabase Auth JWT 令牌，支持 JWKS 缓存。\n\n## 目录\n\n- [✨ 特性](#-特性)\n- [🚀 快速开始](#-快速开始)\n- [🔧 安装](#-安装)\n- [🧩 框架集成示例](#-框架集成示例)\n  - [Axum](#axum)\n  - [Actix Web](#actix-web)\n- [📖 API 概览](#-api-概览)\n- [🏛️ 设计理念](#️-设计理念)\n- [✅ 测试与质量](#-测试与质量)\n- [🤝 贡献](#-贡献)\n- [📜 许可证](#-许可证)\n\n## ✨ 特性\n\n- 🚀 **高性能**: 智能 JWKS 缓存，减少网络请求。\n- 🔒 **安全**: 专为 Supabase Auth ES256 算法优化。\n- 🎯 **简洁**: 框架无关，API 设计简单，易于集成。\n- ⚡ **异步**: 基于 `tokio` 的纯异步设计。\n- 🛡️ **可靠**: 经过充分测试，代码覆盖率 \u003e94%。\n\n## 🚀 快速开始\n\n```rust\nuse supabase_jwt::{Claims, JwksCache};\n\n#[tokio::main]\nasync fn main() -\u003e Result\u003c(), Box\u003cdyn std::error::Error\u003e\u003e {\n    // 1. 初始化 JWKS 缓存\n    // 从你的 Supabase 项目 API 设置中获取 URL\n    let jwks_url = \"https://\u003cyour-project-ref\u003e.supabase.co/auth/v1/jwks\";\n    let jwks_cache = JwksCache::new(jwks_url);\n    \n    // 2. 从请求中获取 Bearer Token\n    let bearer_token = \"Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...\"; // 从 Authorization 头获取\n    \n    // 3. 验证 JWT 并提取 Claims\n    // from_bearer_token 会自动处理 \"Bearer \" 前缀\n    match Claims::from_bearer_token(bearer_token, \u0026jwks_cache).await {\n        Ok(claims) =\u003e {\n            // 4. 访问用户信息\n            println!(\"用户 ID: {}\", claims.user_id());\n            println!(\"邮箱: {:?}\", claims.email());\n            println!(\"角色: {}\", claims.role());\n            println!(\"授权于: {}\", claims.issued_at());\n            println!(\"过期于: {}\", claims.expires_at());\n        }\n        Err(e) =\u003e {\n            eprintln!(\"Token 验证失败: {:?}\", e);\n        }\n    }\n    \n    Ok(())\n}\n```\n\n## 🔧 安装\n\n在你的 `Cargo.toml` 中添加依赖：\n\n```toml\n[dependencies]\nsupabase-jwt = \"0.1.0\" # 请在 crates.io 上检查最新版本\ntokio = { version = \"1.47.0\", features = [\"full\"] }\n```\n\n## 🧩 框架集成示例\n\n### Axum\n\n推荐使用 `axum-extra` 来优雅地提取 Bearer Token。\n\n```rust\nuse axum::{\n    extract::State,\n    http::StatusCode,\n    response::{IntoResponse, Json},\n    routing::get,\n    Router,\n};\nuse axum_extra::headers::{authorization::Bearer, Authorization};\nuse axum_extra::TypedHeader;\nuse supabase_jwt::{Claims, JwksCache};\nuse std::sync::Arc;\n\nasync fn protected_handler(\n    State(jwks_cache): State\u003cArc\u003cJwksCache\u003e\u003e,\n    TypedHeader(Authorization(bearer)): TypedHeader\u003cAuthorization\u003cBearer\u003e\u003e,\n) -\u003e Result\u003cJson\u003cserde_json::Value\u003e, StatusCode\u003e {\n    let claims = Claims::from_token(bearer.token(), \u0026jwks_cache)\n        .await\n        .map_err(|_| StatusCode::UNAUTHORIZED)?;\n    \n    Ok(Json(serde_json::json!({\n        \"user_id\": claims.user_id(),\n        \"email\": claims.email()\n    })))\n}\n\n/*\n// 在你的应用中设置状态\nasync fn run_app() {\n    let jwks_cache = Arc::new(JwksCache::new(\"...\"));\n    let app = Router::new()\n        .route(\"/protected\", get(protected_handler))\n        .with_state(jwks_cache);\n    \n    // 启动服务器...\n}\n*/\n```\n\n### Actix Web\n\n在 Actix Web 中，你可以从请求头中手动提取。\n\n```rust\nuse actix_web::{web, HttpRequest, HttpResponse, Result};\nuse supabase_jwt::{Claims, JwksCache};\n\nasync fn protected_handler(\n    req: HttpRequest,\n    jwks_cache: web::Data\u003cJwksCache\u003e,\n) -\u003e Result\u003cHttpResponse\u003e {\n    let bearer_token = req\n        .headers()\n        .get(\"Authorization\")\n        .and_then(|h| h.to_str().ok())\n        .ok_or_else(|| actix_web::error::ErrorUnauthorized(\"Missing authorization header\"))?;\n\n    let claims = Claims::from_bearer_token(bearer_token, \u0026jwks_cache)\n        .await\n        .map_err(|e| {\n            eprintln!(\"Token validation error: {:?}\", e);\n            actix_web::error::ErrorUnauthorized(\"Invalid token\")\n        })?;\n    \n    Ok(HttpResponse::Ok().json(serde_json::json!({\n        \"user_id\": claims.user_id(),\n        \"role\": claims.role()\n    })))\n}\n```\n\n## 📖 API 概览\n\n`Claims` 结构体提供了便捷的方法来访问 JWT 中的标准和自定义信息。\n\n```rust\n// 基本信息\nlet user_id = claims.user_id();        // 用户 ID (sub)\nlet email = claims.email();            // 邮箱 (email)\nlet role = claims.role();              // 角色 (role)\nlet phone = claims.phone();            // 手机号 (phone)\nlet is_anon = claims.is_anonymous();   // 是否为匿名用户 (is_anonymous)\n\n// 时间戳\nlet issued_at = claims.issued_at();    // 颁发时间 (iat)\nlet expires_at = claims.expires_at();  // 过期时间 (exp)\n\n// 元数据\n// 假设 user_metadata = {\"custom_field\": \"value\"}\nlet custom_field: Option\u003cString\u003e = claims.get_user_metadata(\"custom_field\");\n\n// 假设 app_metadata = {\"feature_enabled\": true}\nlet app_setting: Option\u003cbool\u003e = claims.get_app_metadata(\"feature_enabled\");\n```\n\n`JwksCache` 提供了智能的密钥缓存机制来高效验证令牌。\n\n```rust\nlet jwks_cache = JwksCache::new(\"https://\u003cproject\u003e.supabase.co/auth/v1/jwks\");\n\n// 自动从缓存或网络获取 JWKS\nlet jwks = jwks_cache.get_jwks().await?;\n\n// 查找特定密钥（通常由 from_token 内部调用）\nlet key = jwks_cache.find_key(\"key_id\").await?;\n```\n\n更多详细信息，请参阅 [**docs.rs 上的完整 API 文档**](https://docs.rs/supabase-jwt)。\n\n## 🚀 高级用法\n\n### 错误处理\n\n优雅地处理不同的认证错误是一种良好的实践。`from_bearer_token` 会返回一个详细的 `AuthError` 枚举。\n\n```rust\nuse supabase_jwt::{AuthError, Claims, JwksCache};\n\nasync fn handle_request(bearer_token: \u0026str, jwks_cache: \u0026JwksCache) {\n    match Claims::from_bearer_token(bearer_token, jwks_cache).await {\n        Ok(claims) =\u003e {\n            println!(\"成功为用户 {} 验证令牌\", claims.user_id());\n        }\n        Err(e) =\u003e {\n            eprintln!(\"认证失败: {}\", e);\n            // 处理特定错误的示例\n            match e {\n                AuthError::InvalidToken =\u003e {\n                    // 触发重新认证\n                }\n                AuthError::Verification =\u003e {\n                    // 令牌签名无效，可能存在安全风险\n                }\n                AuthError::JwksError(_) =\u003e {\n                    // 可能是网络问题或 Supabase 服务中断\n                }\n                _ =\u003e {\n                    // 处理其他情况\n                }\n            }\n        }\n    }\n}\n```\n\n### 缓存行为\n\n`JwksCache` 专为高可用性和高性能而设计，内置了智能缓存策略，无需手动配置：\n\n- **正常缓存**：JWKS 的缓存时间为 **24 小时**。\n- **降级缓存**：如果获取新密钥失败（例如，由于网络错误），缓存将继续提供最后一次已知的有效密钥，最长可达 **7 天**。这可以防止您的应用程序在 Supabase Auth 服务暂时不可用时完全失效。\n- **网络超时**：所有到 JWKS 端点的网络请求都有 **5 秒** 的超时设置，以防止您的应用程序被挂起。\n\n\n## 🏛️ 设计理念\n\n本库基于 \"信任 Supabase Auth，专注解析稳定性\" 的设计理念：\n\n- **信任上游**: 相信 Supabase Auth 生成的 token 格式和内容的合法性。\n- **专注解析**: 重点保证解析过程的稳定性和性能。\n- **快速失败**: 对异常 token 立即拒绝，避免过度验证。\n- **缓存优化**: 智能 JWKS 缓存，减少网络开销。\n\n## ✅ 测试与质量\n\n我们非常重视代码质量和可靠性，并通过全面的测试策略来保证。\n\n- **测试覆盖率**: 使用 `cargo-tarpaulin` 达到 **94%+** 的代码覆盖率。\n- **测试用例**: 超过 **100个** 测试用例，覆盖了核心逻辑、边界条件和集成场景。\n- **模拟服务**: 使用 `wiremock` 模拟 Supabase Auth API，确保测试的稳定性和独立性。\n\n你可以使用以下命令运行测试：\n```bash\n# 运行所有测试\ncargo test\n\n# 计算代码覆盖率\ncargo tarpaulin --include-tests\n```\n\n## 🤝 贡献\n\n欢迎提交 Issue 和 Pull Request！在贡献代码前，请确保：\n\n1. 运行 `cargo fmt` 格式化代码。\n2. 运行 `cargo clippy` 检查代码质量。\n3. 运行 `cargo test` 确保所有测试通过。\n\n## 📜 许可证\n\n本项目采用 MIT 或 Apache-2.0 双重许可证。详见 [LICENSE-MIT](LICENSE-MIT) 和 [LICENSE-APACHE](LICENSE-APACHE) 文件。\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkanata996%2Fsupabase-jwt","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkanata996%2Fsupabase-jwt","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkanata996%2Fsupabase-jwt/lists"}