axum_gate/gate/mod.rs
1//! Gate implementation for protecting axum routes with JWT authentication.
2//!
3//! The `Gate` provides a high-level API for adding authentication and authorization
4//! to your axum routes using JWT cookies or bearer tokens. It supports role-based
5//! access control, group-based access control, and fine-grained permission systems.
6//!
7//! # Basic Usage
8//!
9//! ```rust
10//! use axum::{routing::get, Router};
11//! use axum_gate::prelude::*;
12//! use std::sync::Arc;
13//!
14//! # async fn protected_handler() -> &'static str { "Protected!" }
15//! let jwt_codec = Arc::new(JsonWebToken::<JwtClaims<Account<Role, Group>>>::default());
16//! let cookie_template = CookieTemplate::recommended()
17//! .name("auth-token")
18//! .persistent(cookie::time::Duration::hours(24));
19//!
20//! let app = Router::<()>::new()
21//! .route("/admin", get(protected_handler))
22//! .layer(
23//! Gate::cookie("my-app", jwt_codec)
24//! .with_policy(AccessPolicy::<Role, Group>::require_role(Role::Admin))
25//! .with_cookie_template(cookie_template)
26//! );
27//! ```
28//!
29//! # Access Control Examples
30//!
31//! ## Role-Based Access
32//! ```rust
33//! # use axum_gate::prelude::*;
34//! # use std::sync::Arc;
35//! # let jwt_codec = Arc::new(JsonWebToken::<JwtClaims<Account<Role, Group>>>::default());
36//! // Allow only Admin role
37//! let gate = Gate::cookie("my-app", Arc::clone(&jwt_codec))
38//! .with_policy(AccessPolicy::<Role, Group>::require_role(Role::Admin));
39//!
40//! // Allow Admin or Moderator roles
41//! let gate = Gate::cookie("my-app", Arc::clone(&jwt_codec))
42//! .with_policy(
43//! AccessPolicy::<Role, Group>::require_role(Role::Admin)
44//! .or_require_role(Role::Moderator)
45//! );
46//! ```
47//!
48//! ## Hierarchical Access
49//! ```rust
50//! # use axum_gate::prelude::*;
51//! # use std::sync::Arc;
52//! # let jwt_codec = Arc::new(JsonWebToken::<JwtClaims<Account<Role, Group>>>::default());
53//! // Allow User role and all supervisor roles (Reporter, Moderator, Admin)
54//! let gate = Gate::cookie("my-app", jwt_codec)
55//! .with_policy(AccessPolicy::<Role, Group>::require_role_or_supervisor(Role::User));
56//! ```
57//!
58//! ## Permission-Based Access
59//! ```rust
60//! # use axum_gate::prelude::*;
61//! # use std::sync::Arc;
62//! # let jwt_codec = Arc::new(JsonWebToken::<JwtClaims<Account<Role, Group>>>::default());
63//! let gate = Gate::cookie("my-app", jwt_codec)
64//! .with_policy(
65//! AccessPolicy::<Role, Group>::require_permission(PermissionId::from("read:api"))
66//! );
67//! ```
68//! ## Bearer Gate (JWT)
69//! Strict bearer (JWT) example:
70//! ```rust
71//! # use axum::{routing::get, Router};
72//! # use axum_gate::prelude::*;
73//! # use std::sync::Arc;
74//! # async fn handler() {}
75//! let jwt = Arc::new(JsonWebToken::<JwtClaims<Account<Role, Group>>>::default());
76//! let app = Router::<()>::new()
77//! .route("/admin", get(handler))
78//! .layer(
79//! Gate::bearer("my-app", Arc::clone(&jwt))
80//! .with_policy(AccessPolicy::<Role, Group>::require_role(Role::Admin))
81//! );
82//! ```
83//!
84//! Optional user context (never blocks; handlers must enforce access):
85//! ```rust
86//! # use axum_gate::prelude::*;
87//! # use std::sync::Arc;
88//! let jwt = Arc::new(JsonWebToken::<JwtClaims<Account<Role, Group>>>::default());
89//! let gate = Gate::bearer::<JsonWebToken<JwtClaims<Account<Role, Group>>>, Role, Group>("my-app", jwt).allow_anonymous_with_optional_user();
90//! // Inserts Option<Account<Role, Group>> and Option<RegisteredClaims> into request extensions.
91//! ```
92//!
93use self::bearer::BearerGate;
94use self::cookie::CookieGate;
95use self::oauth2::OAuth2Gate;
96use crate::accounts::Account;
97use crate::authz::AccessHierarchy;
98use crate::codecs::Codec;
99use crate::codecs::jwt::JwtClaims;
100
101use std::sync::Arc;
102
103pub mod bearer;
104pub mod cookie;
105pub mod oauth2;
106
107/// Main entry point for creating authentication gates.
108///
109/// Gates protect your axum routes from unauthorized access using JWT tokens.
110/// All requests are denied by default unless explicitly granted access through
111/// an access policy. Choose between cookie-based gates for web applications
112/// and bearer token gates for APIs and SPAs.
113#[derive(Clone)]
114pub struct Gate;
115
116impl Gate {
117 /// Creates a new cookie-based gate that denies all access by default.
118 ///
119 /// Use this for web applications where you want automatic token handling
120 /// through HTTP-only cookies. Cookie gates provide CSRF protection and
121 /// work seamlessly with browser-based authentication flows.
122 ///
123 /// Attach an access policy using `with_policy()` to grant access. This secure-by-default
124 /// approach ensures no routes are exposed until you explicitly configure a policy.
125 ///
126 /// # Arguments
127 /// * `issuer` - The JWT issuer identifier for your application
128 /// * `codec` - JWT codec for encoding/decoding tokens
129 ///
130 /// # Example
131 /// ```rust
132 /// # use axum_gate::prelude::*;
133 /// # use std::sync::Arc;
134 /// let jwt_codec = Arc::new(JsonWebToken::<JwtClaims<Account<Role, Group>>>::default());
135 /// let policy = AccessPolicy::<Role, Group>::require_role(Role::Admin);
136 ///
137 /// let gate = Gate::cookie("my-app", jwt_codec)
138 /// .with_policy(policy);
139 /// ```
140 pub fn cookie<C, R, G>(issuer: &str, codec: Arc<C>) -> CookieGate<C, R, G>
141 where
142 C: Codec,
143 R: AccessHierarchy + Eq + std::fmt::Display,
144 G: Eq,
145 {
146 CookieGate::new_with_codec(issuer, codec)
147 }
148
149 /// Creates a new bearer-header based gate that denies all access by default.
150 ///
151 /// Use this for APIs, SPAs, and mobile applications where you need explicit
152 /// token management. Bearer token gates require clients to include tokens
153 /// in the `Authorization: Bearer <token>` header, providing fine-grained
154 /// control over token lifecycle and excellent support for API integrations.
155 ///
156 /// This variant protects routes by expecting an `Authorization: Bearer <token>`
157 /// header. Missing or invalid bearer tokens result in `401 Unauthorized`.
158 ///
159 /// Optional mode is supported via `allow_anonymous_with_optional_user()`. In optional mode,
160 /// requests are always forwarded and the layer inserts `Option<Account<R, G>>` and
161 /// `Option<RegisteredClaims>` (Some only when the token is valid). You can also transition to
162 /// a static shared-secret mode via `.with_static_token("...")`.
163 ///
164 /// # Arguments
165 /// * `issuer` - The JWT issuer identifier for your application
166 /// * `codec` - JWT codec for encoding/decoding tokens
167 pub fn bearer<C, R, G>(
168 issuer: &str,
169 codec: Arc<C>,
170 ) -> BearerGate<C, R, G, bearer::JwtConfig<R, G>>
171 where
172 C: Codec,
173 R: AccessHierarchy + Eq + std::fmt::Display,
174 G: Eq + Clone,
175 {
176 // Delegates to the BearerGate builder (to be implemented in bearer module).
177 BearerGate::new_with_codec(issuer, codec)
178 }
179
180 /// Creates a new OAuth2-based gate builder using the `oauth2` crate.
181 ///
182 /// This returns an OAuth2 flow builder that can mount `/login` and `/callback` routes and,
183 /// on successful callback, will mint a first-party JWT via the existing CookieGate.
184 pub fn oauth2<R, G>() -> OAuth2Gate<R, G>
185 where
186 R: AccessHierarchy + Eq + std::fmt::Display + Send + Sync + 'static,
187 G: Eq + Clone + Send + Sync + 'static,
188 {
189 OAuth2Gate::new()
190 }
191
192 /// Creates a new OAuth2-based gate builder preconfigured with a JWT encoder.
193 ///
194 /// # Arguments
195 /// * `issuer` - JWT issuer for your application
196 /// * `codec` - JWT codec used to mint tokens for the first‑party cookie
197 /// * `ttl_secs` - Expiration (seconds) for issued JWTs
198 pub fn oauth2_with_jwt<C, R, G>(issuer: &str, codec: Arc<C>, ttl_secs: u64) -> OAuth2Gate<R, G>
199 where
200 C: Codec<Payload = JwtClaims<Account<R, G>>> + Send + Sync + 'static,
201 R: AccessHierarchy + Eq + std::fmt::Display + Send + Sync + 'static,
202 G: Eq + Clone + Send + Sync + 'static,
203 {
204 OAuth2Gate::new().with_jwt_codec(issuer, codec, ttl_secs)
205 }
206}