axum_login/
lib.rs

1//! # Overview
2//!
3//! This crate provides user identification, authentication, and authorization
4//! as a `tower` middleware for `axum`.
5//!
6//! It offers:
7//!
8//! - **User Identification, Authentication, and Authorization**: Leverage
9//!   [`AuthSession`] to easily manage authentication and authorization. This is
10//!   also an extractor, so it can be used directly in your `axum` handlers.
11//! - **Support for Arbitrary Users and Backends**: Applications implement a
12//!   couple of traits, [`AuthUser`] and [`AuthnBackend`], allowing for any user
13//!   type and any user management backend. Your database? Yep. LDAP? Sure. An
14//!   auth provider? You bet.
15//! - **User and Group Permissions**: Authorization is supported via the
16//!   [`AuthzBackend`] trait, which allows applications to define custom
17//!   permissions. Both user and group permissions are supported.
18//! - **Convenient Route Protection**: Middleware for protecting access to
19//!   routes are provided via the [`login_required`] and [`permission_required`]
20//!   macros. Or bring your own by using [`AuthSession`] directly with
21//!   [`from_fn`](axum::middleware::from_fn).
22//! - **Rock-solid Session Management**: Uses [`tower-sessions`](tower_sessions)
23//!   for high-performing and ergonomic session management. *Look ma, no
24//!   deadlocks!*
25//!
26//! # Usage
27//!
28//! Applications implement two traits, and optionally a third, to enable login
29//! workflows: [`AuthUser`] and [`AuthnBackend`]. Respectively, these define a
30//! minimal interface for arbitrary user types and an interface with an
31//! arbitrary user management backend.
32//!
33//! ```rust
34//! use std::collections::HashMap;
35//!
36//! use axum_login::{AuthUser, AuthnBackend, UserId};
37//!
38//! #[derive(Debug, Clone)]
39//! struct User {
40//!     id: i64,
41//!     pw_hash: Vec<u8>,
42//! }
43//!
44//! impl AuthUser for User {
45//!     type Id = i64;
46//!
47//!     fn id(&self) -> Self::Id {
48//!         self.id
49//!     }
50//!
51//!     fn session_auth_hash(&self) -> &[u8] {
52//!         &self.pw_hash
53//!     }
54//! }
55//!
56//! #[derive(Clone, Default)]
57//! struct Backend {
58//!     users: HashMap<i64, User>,
59//! }
60//!
61//! #[derive(Clone)]
62//! struct Credentials {
63//!     user_id: i64,
64//! }
65//!
66//! impl AuthnBackend for Backend {
67//!     type User = User;
68//!     type Credentials = Credentials;
69//!     type Error = std::convert::Infallible;
70//!
71//!     async fn authenticate(
72//!         &self,
73//!         Credentials { user_id }: Self::Credentials,
74//!     ) -> Result<Option<Self::User>, Self::Error> {
75//!         Ok(self.users.get(&user_id).cloned())
76//!     }
77//!
78//!     async fn get_user(
79//!         &self,
80//!         user_id: &UserId<Self>,
81//!     ) -> Result<Option<Self::User>, Self::Error> {
82//!         Ok(self.users.get(user_id).cloned())
83//!     }
84//! }
85//! ```
86//!
87//! Here we've provided implementations for our own user type and a backend (in
88//! this case, we use a `HashMap` only as a proxy for something like a
89//! database). If we also wanted to support authorization, we could extend with
90//! this an implementation of [`AuthzBackend`].
91//!
92//! It's worth covering a couple of these methods in a little more detail:
93//!
94//! - `session_auth_hash`, which is used to validate the session; in our example
95//!   we use a user's password hash, which means changing passwords will
96//!   invalidate the session.
97//! - `get_user`, which is used to load the user from the backend into the
98//!   session.
99//!
100//! Note that our example is not realistic and is meant only to provide an
101//! illustration of the API. For instance, our implementation of `authenticate`
102//! would likely use proper credentials, and not an ID, to positively identify
103//! and authenticate a user in a real backend system.
104//!
105//! ## Writing handlers
106//!
107//! With the traits implemented, we can write `axum` handlers, leveraging
108//! [`AuthSession`] to manage authentication and authorization workflows.
109//! Because `AuthSession` is an extractor, we can use it directly in our
110//! handlers.
111//!
112//! ```rust
113//! # use std::collections::HashMap;
114//! #
115//! # use axum_login::{AuthUser, AuthnBackend, UserId};
116//! #
117//! # #[derive(Debug, Clone)]
118//! # struct User {
119//! #     id: i64,
120//! #     pw_hash: Vec<u8>,
121//! # }
122//! #
123//! # impl AuthUser for User {
124//! #     type Id = i64;
125//! #
126//! #     fn id(&self) -> Self::Id {
127//! #         self.id
128//! #     }
129//! #
130//! #     fn session_auth_hash(&self) -> &[u8] {
131//! #         &self.pw_hash
132//! #     }
133//! # }
134//! #
135//! # #[derive(Clone)]
136//! # struct Backend {
137//! #     users: HashMap<i64, User>,
138//! # }
139//! #
140//! # #[derive(Clone)]
141//! # struct Credentials {
142//! #     user_id: i64,
143//! # }
144//! #
145//! # impl AuthnBackend for Backend {
146//! #     type User = User;
147//! #     type Credentials = Credentials;
148//! #     type Error = std::convert::Infallible;
149//! #
150//! #     async fn authenticate(
151//! #         &self,
152//! #         Credentials { user_id }: Self::Credentials,
153//! #     ) -> Result<Option<Self::User>, Self::Error> {
154//! #         Ok(self.users.get(&user_id).cloned())
155//! #     }
156//! #
157//! #     async fn get_user(
158//! #         &self,
159//! #         user_id: &UserId<Self>,
160//! #     ) -> Result<Option<Self::User>, Self::Error> {
161//! #         Ok(self.users.get(user_id).cloned())
162//! #     }
163//! # }
164//! use axum::{
165//!     http::StatusCode,
166//!     response::{IntoResponse, Redirect},
167//!     Form,
168//! };
169//!
170//! type AuthSession = axum_login::AuthSession<Backend>;
171//!
172//! async fn login(
173//!     mut auth_session: AuthSession,
174//!     Form(creds): Form<Credentials>,
175//! ) -> impl IntoResponse {
176//!     let user = match auth_session.authenticate(creds.clone()).await {
177//!         Ok(Some(user)) => user,
178//!         Ok(None) => return StatusCode::UNAUTHORIZED.into_response(),
179//!         Err(_) => return StatusCode::INTERNAL_SERVER_ERROR.into_response(),
180//!     };
181//!
182//!     if auth_session.login(&user).await.is_err() {
183//!         return StatusCode::INTERNAL_SERVER_ERROR.into_response();
184//!     }
185//!
186//!     Redirect::to("/protected").into_response()
187//! }
188//! ```
189//!
190//! This handler uses a `Form` extractor to retrieve credentials and then uses
191//! them to authenticate with our backend. When successful we get back a user
192//! and can then log the user in. Such a workflow can be adapted to the specific
193//! needs of an application.
194//!
195//! ## Protecting routes
196//!
197//! Access to routes can be controlled with [`login_required`] and
198//! [`permission_required`]. These produce middleware which may be used directly
199//! with application routes.
200//!
201//! ```rust
202//! # use std::collections::HashMap;
203//! #
204//! # use axum_login::{AuthUser, AuthnBackend, UserId};
205//! #
206//! # #[derive(Debug, Clone)]
207//! # struct User {
208//! #     id: i64,
209//! #     pw_hash: Vec<u8>,
210//! # }
211//! #
212//! # impl AuthUser for User {
213//! #     type Id = i64;
214//! #
215//! #     fn id(&self) -> Self::Id {
216//! #         self.id
217//! #     }
218//! #
219//! #     fn session_auth_hash(&self) -> &[u8] {
220//! #         &self.pw_hash
221//! #     }
222//! # }
223//! #
224//! # #[derive(Clone)]
225//! # struct Backend {
226//! #     users: HashMap<i64, User>,
227//! # }
228//! #
229//! # #[derive(Clone)]
230//! # struct Credentials {
231//! #     user_id: i64,
232//! # }
233//! #
234//! # impl AuthnBackend for Backend {
235//! #     type User = User;
236//! #     type Credentials = Credentials;
237//! #     type Error = std::convert::Infallible;
238//! #
239//! #     async fn authenticate(
240//! #         &self,
241//! #         Credentials { user_id }: Self::Credentials,
242//! #     ) -> Result<Option<Self::User>, Self::Error> {
243//! #         Ok(self.users.get(&user_id).cloned())
244//! #     }
245//! #
246//! #     async fn get_user(
247//! #         &self,
248//! #         user_id: &UserId<Self>,
249//! #     ) -> Result<Option<Self::User>, Self::Error> {
250//! #         Ok(self.users.get(user_id).cloned())
251//! #     }
252//! # }
253//! use axum::{routing::get, Router};
254//! use axum_login::login_required;
255//!
256//! fn protected_routes() -> Router {
257//!     Router::new()
258//!         .route(
259//!             "/protected",
260//!             get(|| async { "Gotta be logged in to see me!" }),
261//!         )
262//!         .route_layer(login_required!(Backend, login_url = "/login"))
263//! }
264//! ```
265//!
266//! Routes defined in this way can be protected by the middleware, in this case
267//! ensuring that a user is logged before accessing the resource. When a user is
268//! not logged in, the user agent is redirected to the provided login URL.
269//!
270//! Likewise, [`permission_required`] can be used to require user or
271//! group permissions in order to access the protected resource.
272//!
273//! ## Setting up an auth service
274//!
275//! In order to make use of this within our `axum` application, we establish a
276//! `tower` service which provides a middleware that attaches `AuthSession` as a
277//! request extension.
278//!
279//! ```rust,no_run
280//! # use std::collections::HashMap;
281//! #
282//! # use axum_login::{AuthUser, AuthnBackend, UserId};
283//! #
284//! # #[derive(Debug, Clone)]
285//! # struct User {
286//! #     id: i64,
287//! #     pw_hash: Vec<u8>,
288//! # }
289//! #
290//! # impl AuthUser for User {
291//! #     type Id = i64;
292//! #
293//! #     fn id(&self) -> Self::Id {
294//! #         self.id
295//! #     }
296//! #
297//! #     fn session_auth_hash(&self) -> &[u8] {
298//! #         &self.pw_hash
299//! #     }
300//! # }
301//! #
302//! # #[derive(Clone, Default)]
303//! # struct Backend {
304//! #     users: HashMap<i64, User>,
305//! # }
306//! #
307//! # #[derive(Clone)]
308//! # struct Credentials {
309//! #     user_id: i64,
310//! # }
311//! #
312//! # impl AuthnBackend for Backend {
313//! #     type User = User;
314//! #     type Credentials = Credentials;
315//! #     type Error = std::convert::Infallible;
316//! #
317//! #     async fn authenticate(
318//! #         &self,
319//! #         Credentials { user_id }: Self::Credentials,
320//! #     ) -> Result<Option<Self::User>, Self::Error> {
321//! #         Ok(self.users.get(&user_id).cloned())
322//! #     }
323//! #
324//! #     async fn get_user(
325//! #         &self,
326//! #         user_id: &UserId<Self>,
327//! #     ) -> Result<Option<Self::User>, Self::Error> {
328//! #         Ok(self.users.get(user_id).cloned())
329//! #     }
330//! # }
331//! use axum::{
332//!     routing::{get, post},
333//!     Router,
334//! };
335//! use axum_login::{
336//!     login_required,
337//!     tower_sessions::{MemoryStore, SessionManagerLayer},
338//!     AuthManagerLayerBuilder,
339//! };
340//!
341//! #[tokio::main]
342//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
343//!     // Session layer.
344//!     let session_store = MemoryStore::default();
345//!     let session_layer = SessionManagerLayer::new(session_store);
346//!
347//!     // Auth service.
348//!     let backend = Backend::default();
349//!     let auth_layer = AuthManagerLayerBuilder::new(backend, session_layer).build();
350//!
351//!     let app = Router::new()
352//!         .route("/protected", get(todo!()))
353//!         .route_layer(login_required!(Backend, login_url = "/login"))
354//!         .route("/login", post(todo!()))
355//!         .route("/login", get(todo!()))
356//!         .layer(auth_layer);
357//!
358//!     let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
359//!     axum::serve(listener, app.into_make_service()).await?;
360//!
361//!     Ok(())
362//! }
363//! ```
364//!
365//! ## One more thing
366//!
367//! While this overview of the API aims to give you a sense of how the crate
368//! works and how you might use it with your own applications, these snippets
369//! are incomplete and as such it's recommended to review a comprehensive
370//! implementation as well.
371//!
372//! A complete example can be found in [`examples/sqlite.rs`](https://github.com/maxcountryman/axum-login/blob/main/examples/sqlite).
373#![warn(
374    clippy::all,
375    nonstandard_style,
376    future_incompatible,
377    missing_docs,
378    missing_debug_implementations
379)]
380#![forbid(unsafe_code)]
381
382pub use axum;
383pub use backend::{AuthUser, AuthnBackend, AuthzBackend, UserId};
384#[doc(hidden)]
385pub use middleware::url_with_redirect_query;
386pub use service::{AuthManager, AuthManagerLayer, AuthManagerLayerBuilder};
387pub use session::{AuthSession, Error};
388pub use tower_sessions;
389pub use tracing;
390
391mod backend;
392mod extract;
393mod middleware;
394mod service;
395mod session;