🪪 Session-based user authentication for Axum.
axum-login
is a Tower middleware providing session-based user authentication for axum
applications.
- Decouples user storage from authentication
- Supports arbitrary user types and arbitrary storage backends
- Provides methods for: logging in, logging out, and accessing current user
- Wraps
axum-sessions
to provide flexible sessions - Leverages
tower_http::auth::RequireAuthorizationLayer
to protect routes
Note
axum-login
implements a fundamental pattern for user authentication, however some features may be missing. Folks are encouraged to make suggestions for extensions to the library.
To use the crate in your project, add the following to your Cargo.toml
file:
[dependencies]
axum-login = "0.2.0"
axum
applications can use the middleware via the auth layer.
use axum::{response::IntoResponse, routing::get, Extension, Router};
use axum_login::{
axum_sessions::{async_session::MemoryStore, SessionLayer},
AuthLayer, AuthUser, RequireAuthorizationLayer, SqliteStore,
};
use rand::Rng;
use sqlx::sqlite::SqlitePoolOptions;
#[derive(Debug, Default, Clone, sqlx::FromRow)]
struct User {
id: i64,
password_hash: String,
name: String,
}
impl AuthUser for User {
fn get_id(&self) -> String {
format!("{}", self.id)
}
fn get_password_hash(&self) -> String {
self.password_hash.clone()
}
}
type AuthContext = axum_login::extractors::AuthContext<User, SqliteStore<User>>;
#[tokio::main]
async fn main() {
let secret = rand::thread_rng().gen::<[u8; 64]>();
let session_store = MemoryStore::new();
let session_layer = SessionLayer::new(session_store, &secret).with_secure(false);
let pool = SqlitePoolOptions::new()
.connect("user_store.db")
.await
.unwrap();
let user_store = SqliteStore::<User>::new(pool);
let auth_layer = AuthLayer::new(user_store, &secret);
async fn login_handler(mut auth: AuthContext) {
let pool = SqlitePoolOptions::new()
.connect("user_store.db")
.await
.unwrap();
let mut conn = pool.acquire().await.unwrap();
let user: User = sqlx::query_as("select * from users where id = 1")
.fetch_one(&mut conn)
.await
.unwrap();
auth.login(&user).await.unwrap();
}
async fn logout_handler(mut auth: AuthContext) {
dbg!("Logging out user: {}", &auth.current_user);
auth.logout().await;
}
async fn protected_handler(Extension(user): Extension<User>) -> impl IntoResponse {
format!("Logged in as: {}", user.name)
}
let app = Router::new()
.route("/protected", get(protected_handler))
.route_layer(RequireAuthorizationLayer::<User>::login())
.route("/login", get(login_handler))
.route("/logout", get(logout_handler))
.layer(auth_layer)
.layer(session_layer);
axum::Server::bind(&"0.0.0.0:3000".parse().unwrap())
.serve(app.into_make_service())
.await
.unwrap();
}
You can find this example as well as other example projects in the example directory.
See the crate documentation for more usage information.