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;