axum_oidc_layer/lib.rs
1//! Axum OIDC Authentication Layer
2//!
3//! This crate provides a configurable OIDC (OpenID Connect) authentication layer for Axum applications.
4//! It supports JWT token validation with caching for improved performance and includes pluggable cache backends.
5//!
6//! # Features
7//!
8//! - **Automatic Token Validation**: Validates JWT tokens against OIDC provider's JWKS
9//! - **Multi-tier Caching**: Caches OIDC configuration, individual JWK keys, and token validation results
10//! - **Type-safe Cache Keys**: Prevents mixing different cache key types
11//! - **Pluggable Cache Backends**: Implement custom cache strategies (Redis, database, etc.)
12//! - **Configurable TTL**: Control cache lifetimes for different data types
13//! - **Proper Error Handling**: Comprehensive error types with appropriate HTTP responses
14//!
15//! # Quick Start
16//!
17//! ```rust,ignore
18//! use axum_oidc_layer::{OidcAuthenticationLayer, AuthenticationConfigProvider, Claims};
19//! use std::time::Duration;
20//!
21//! #[derive(Clone)]
22//! struct MyConfig;
23//!
24//! impl AuthenticationConfigProvider for MyConfig {
25//! fn get_provider_url(&self) -> String {
26//! "https://your-oidc-provider.com".to_string()
27//! }
28//!
29//! fn get_openid_configuration_url(&self) -> Option<String> {
30//! None // Uses default /.well-known/openid-configuration
31//! }
32//! }
33//!
34//! let layer = OidcAuthenticationLayer::<MyConfig, Claims>::new(MyConfig);
35//! ```
36
37use axum_core::{
38 extract::FromRequestParts,
39 response::{IntoResponse, Response},
40};
41use http::request::Parts;
42use reqwest::StatusCode;
43
44// Module declarations
45pub mod cache;
46pub mod config;
47pub mod error;
48pub mod jwks;
49pub mod layer;
50pub mod token;
51pub mod validation;
52
53// Public re-exports for convenience
54pub use cache::{ConfigCacheKey, InMemoryCache, JwkCacheKey, JwksCache, TokenCacheKey};
55pub use config::{AuthenticationConfigProvider, OidcConfiguration};
56pub use error::OidcError;
57pub use layer::{OidcAuthenticationLayer, OidcAuthenticationService};
58
59/// Default JWT claims structure.
60///
61/// This provides a basic claims structure that can be used out-of-the-box.
62/// For custom claims, implement your own struct that derives `serde::Deserialize` and `serde::Serialize`.
63#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
64pub struct Claims {
65 /// Subject (user identifier)
66 pub sub: String,
67}
68
69impl<S: Sync> FromRequestParts<S> for Claims {
70 type Rejection = AuthenticationError;
71
72 async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result<Self, Self::Rejection> {
73 parts
74 .extensions
75 .get::<Self>()
76 .cloned()
77 .ok_or(AuthenticationError::InvalidToken)
78 }
79}
80
81/// Legacy authentication error type for backward compatibility.
82///
83/// For new code, prefer using `OidcError` directly.
84#[derive(Debug, Clone)]
85pub enum AuthenticationError {
86 /// Token is invalid or malformed
87 InvalidToken,
88 /// Authorization header is missing
89 MissingToken,
90}
91
92impl std::fmt::Display for AuthenticationError {
93 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
94 match self {
95 Self::InvalidToken => write!(f, "Invalid token"),
96 Self::MissingToken => write!(f, "Missing token"),
97 }
98 }
99}
100
101impl IntoResponse for AuthenticationError {
102 fn into_response(self) -> axum_core::response::Response {
103 let (status, message) = match self {
104 Self::InvalidToken => {
105 (StatusCode::UNAUTHORIZED, "Invalid authorization token")
106 }
107 Self::MissingToken => {
108 (StatusCode::UNAUTHORIZED, "Missing authorization token")
109 }
110 };
111
112 Response::builder()
113 .status(status)
114 .body(axum_core::body::Body::from(message))
115 .unwrap_or_default()
116 }
117}
118
119impl std::error::Error for AuthenticationError {}