🪪 Session-based user authentication for Axum.

Overview

axum-login

🪪 Session-based user authentication for Axum.

🎨 Overview

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.

📦 Install

To use the crate in your project, add the following to your Cargo.toml file:

[dependencies]
axum-login = "0.2.0"

🤸 Usage

axum applications can use the middleware via the auth layer.

axum Example

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.

Comments
  • Redirect unauthenticated user to login page.

    Redirect unauthenticated user to login page.

    I have been hoping to replace my custom/fragile middleware I built around axum-sessions with axum-login. One of the things I cannot figure out with this library is how to redirect an unauthenticated user to the login page.

    • user tries to go to "example.com/requires_auth"
    • user is redirected to "example.com/login?next_url=/requires_auth"
    • user logs in
    • user is redirected back to "example.com/requries_auth"

    Right now, because of how "requries_auth" attempts to extract a user, there is no way to redirect when there is no user available.

    Do you have a recommendation or am I going about this all wrong?

    enhancement help wanted good first issue 
    opened by cldershem 12
  • Can't use `self.pool` in impl of UserStore<CustomRole> for PostgresStore<CustomUser>

    Can't use `self.pool` in impl of UserStore for PostgresStore

    use axum::async_trait;
    use axum_login::{secrecy::SecretVec, AuthUser};
    
    #[derive(Clone, Debug, PartialEq, PartialOrd, sqlx::Type)]
    pub enum UserRole {
        Standard,
        Admin,
    }
    
    #[derive(Clone, Debug, sqlx::FromRow, sqlx::Decode)]
    pub struct User {
        id: i32,
        username: String,
        password_hash: String,
        role: UserRole,
    }
    
    impl AuthUser<UserRole> for User {
        fn get_id(&self) -> String {
            format!("{}", self.id)
        }
    
        fn get_password_hash(&self) -> SecretVec<u8> {
            SecretVec::new(self.password_hash.clone().into())
        }
    
        fn get_role(&self) -> Option<UserRole> {
            Some(self.role.clone())
        }
    }
    
    #[async_trait]
    impl axum_login::UserStore<UserRole> for axum_login::PostgresStore<User> {
        type User = User;
    
        async fn load_user(&self, user_id: &str) -> Result<Option<Self::User>, eyre::Report> {
            let mut connection = self.pool.acquire() ...
        }
    }
    

    When I try to use self.pool.acquire in the load_user, I get error that self.pool is private. How can I implement load_user without access to the database pool

    opened by sean-clarke 6
  • Implement a role-based `RequireAuthorizationLayer`

    Implement a role-based `RequireAuthorizationLayer`

    The layer should not only check the login and password, but also if the user has the right role.

    @maxcountryman I understand that your idea is a RequireAuthorizarionLayer separate from the normal login one - the difference being it would also check the role of the user?

    opened by Czocher 6
  • Problem with async_sqlx_session

    Problem with async_sqlx_session

    There seems to be a problem when using async_sqlx_session. When using jbr/async_sqlx_session, the sqlx version is up to date but one can only use async_std whereas axum uses tokio. When using CyrusAlbright/async_sqlx_session, it's possible to use tokio but the sqlx version is not up to date and for instance the PostgresSessionStore cannot be instanciated through the from_client() function. Do you know if there is a way to solve this problem ? Thanks a lot for your answer and for your awesome work.

    opened by LeonGGX 6
  • How to redirect with the auth headers/cookie?

    How to redirect with the auth headers/cookie?

    Firstly, thank you for creating this library!

    I'm implementing an OAuth flow where the provider redirects back to our server, we exchange the code for a token, login the user, then redirect to another internal route e.g.:

    async fn oauth_callback(mut auth: AuthContext) -> impl IntoResponse {
         // I've omitted the oauth details such as code exchange for the token
         let user = find_user_somehow();
    
        // login the user
        auth.login(&user).await.unwrap();
        
        // This redirect causes the session to be lost, I think we need to pass along the headers that contain the cookie?
        Redirect::to("/")
    }
    
    // in main
    let app = Router::new()
       .route("/auth/google/callback", get(oauth_callback))
    

    Axum has an example of the OAuth flow, which looks like this:

        // Create a new session filled with user data
        let mut session = Session::new();
        session.insert("user", &user).unwrap();
    
        // Store session and get corresponding cookie
        let cookie = store.store_session(session).await.unwrap().unwrap();
    
        // Build the cookie
        let cookie = format!("{}={}; SameSite=Lax; Path=/", COOKIE_NAME, cookie);
    
        // Set cookie
        let mut headers = HeaderMap::new();
        headers.insert(SET_COOKIE, cookie.parse().unwrap());
    
        // Redirect with headers
        (headers, Redirect::to("/"))
    

    How can we redirect and preserve the session? Can we get the headers after the auth.login(...) call?

    opened by Rigellute 5
  • How do I create my own store

    How do I create my own store

    I'm using Sea-ORM for a project of mine and I wanted use axum-login to handle authentication. I'm using postgres as the database so I know I can use the postgres feature. But I was wondering if I can create my own store using Sea-ORM. If so, how would I go about doing that?

    I tried implementing it on my own using memory_store.rs and sql_store.rs as examples but the compiler keeps complaining about AuthUser not being satisfied for my User struct (even though I believe I've implemented it). Can you give me some pointers? Thanks.

    opened by mtelahun 4
  • Integration tests setup

    Integration tests setup

    • major: directory structure rework to allow for easy installation of the package for testing purposes
    • docker-compose file with Postgres and Mysql
    • migrations creating test users table
    • fixture with random user to use in tests

    This PR doesn't really contain a lot of tests, the paramount goal was to setup a framework of sorts to allow writing such tests in the first place.

    opened by czotomo 3
  • Support `Sled` key-value store as a `Store`

    Support `Sled` key-value store as a `Store`

    I recently came across Sled when I searched for other possibilities for persistent application state. I think it would be nice to support it as an alternative for sqlx.

    enhancement help wanted 
    opened by Czocher 3
  • I wrote a prototype axum/tower authorization layer for combining auth logic using AND/OR, thoughts on similiar features in axum-login?

    I wrote a prototype axum/tower authorization layer for combining auth logic using AND/OR, thoughts on similiar features in axum-login?

    Hi, sorry for long title. I switched my graphql api on my personal project to a rest api and wrote this for describing authorization using AND/OR logic like graphql. It's not very ergonomic, also I use BoxFuture because I couldn't quite figure out how to write the future by hand. But, I was wondering if you were interested in a similar feature for axum-login or if one already exists. I think it would be good for the ecosystem to have one broad axum auth toolset, that let users describe authorization in a varied way. https://github.com/sjud/axum_guard_logic Warm regards, thank you for your FOSS contribution

    opened by sjud 3
  • Idea: `AuthUser` should not be `Clone` and should require the `Zeroize` trait on the `password` field

    Idea: `AuthUser` should not be `Clone` and should require the `Zeroize` trait on the `password` field

    Currently AuthUser requires the Clone trait to be implemented on the User struct. This is not bad per-say but is not good in terms of secret management hygiene.

    I suggest the AuthUser should handle the password hash very carefully and require the password field to be Zeroizable, so that the password is not cloned nor left in memory. Returning a &str instead would possibly also be beneficial rather than String.

    This is possibly a breaking change.

    Idea: AuthUser can return a SecretString of the password.

    help wanted 
    opened by Czocher 3
  • Add oauth example

    Add oauth example

    The oauth callback -> login -> redirect to /protected is not currently working as expected: we print that the user has logged in but get a 401 on /protected.

    You will need to set up your own Google OAuth Credentials to get this example working. I tried using https://testoauth.com/ but the token exchange endpoint seems non-standard so the oauth client cannot parse the response.

    opened by Rigellute 3
  • Allow crate users to pick the `sqlx` runtime.

    Allow crate users to pick the `sqlx` runtime.

    Currently, axum-login locks users into the tokio & rustls runtime (runtime-tokio-rustls). This PR lets them specify their own, removing the sqlx feature and introducing six new ones in its stead:

    • sqlx-runtime-actix-native-tls
    • sqlx-runtime-async-std-native-tls
    • sqlx-runtime-tokio-native-tls
    • sqlx-runtime-actix-rustls
    • sqlx-runtime-async-std-rustls
    • sqlx-runtime-tokio-rustls

    This should solve https://github.com/maxcountryman/axum-login/issues/5.


    Note: I tested omitting the runtime feature and my project, a consumer of axum-login, still compiled, which is strange. I've only just begun to integrate axum-login, so it might break once I use more of it, but it bears exploring.

    Update: It still compiled because my project uses sqlx as well. Referencing axum-login without specifying a runtime in a project that doesn't fails as expected. Perhaps this ought to be mentioned in the documentation somewhere.

    opened by dsaghliani 11
  • Default query for SqlxStore does not work with MySQL

    Default query for SqlxStore does not work with MySQL

    SqlX docs:

    Bind parameters in the SQL string are specific to the database backend:
        Postgres: $N where N is the 1-based positional argument index
        MySQL: ? which matches arguments in order that it appears in the query
    
    bug help wanted good first issue 
    opened by czotomo 1
  • Support LDAP as a Store

    Support LDAP as a Store

    Hi, Im looking to use the crate with an Ldap backend, however, there is mismatch with the current design.

    As I understand it a UserStore expects to do a lookup using the user_id which provides the User struct with the password hash, which then gets compared by the login function.

    Ldap works differently , the username and password are sent to the server and tested against the stored password server side and a response is returned.

    Im not an LDAP expert but if I remember correctly there is a way to configure the Ldap server to return passwords, however, I believe it is not a recommended practice, also this means you have to connect to the server as a privileged user, not a good idea.

    changing UserStore to have the function something like async fn load_user(&self, user_id: &str, password: Option<&str>)

    Would there be a way to support this different way of authentication?

    Code sample using the ldap3 crate

    ldap.simple_bind(&dn, &password).await

    this then returns a result code (int) and a possibly empty result string.

    let ldap_url=std::env::var("LDAP")
            .unwrap_or("ldap://localhost:3389".to_string());
        //ldaps 3636
        let connect= LdapConnAsync::new(&ldap_url).await;
        match connect {
            Ok((conn, mut ldap)) => {
                ldap3::drive!(conn);
                let dn = format!("uid={},ou=people,dc=example,dc=com", username);
                match ldap.simple_bind(&dn, &password).await {
                    Ok(res) => {
                        if res.rc==0 {
                            println!("Login Success");
                            println!("Code {} Message {}", res.rc, res.text.len());
                        } else {
                            //Code 49 bad user or password
                            //Code 53 account inactivated
                            println!("Code {} Message {}", res.rc, res.text);
                        }
                    }
                    Err(err) => {
                        info!("Ldap Search Error {:?}",err);
                    }
                }
            },
            Err(err) => {
                info!("Ldap Connect Error {:?}",err);
            }
        }
    
    enhancement help wanted good first issue 
    opened by apps4uco 4
  • Support Sea-ORM as a store

    Support Sea-ORM as a store

    Sea-ORM is a popular async ORM that uses sqlx under the hood. There is a discussion about creating a store for it, but it would be awesome if axum-login supports this out of the box.

    enhancement help wanted good first issue 
    opened by limjiayi 2
  • Support `rustqlite` as an alternative `Store`

    Support `rustqlite` as an alternative `Store`

    Rustqlite seems like a good alternative for the sqlx sqlite one since it supports sqlite on a far better level. I believe it would be beneficial to support it separately.

    enhancement help wanted 
    opened by Czocher 2
Owner
Max Countryman
Distributed systems, functional programming, cloud computing.
Max Countryman
axum-serde is a library that provides multiple serde-based extractors and responders for the Axum web framework.

axum-serde ?? Overview axum-serde is a library that provides multiple serde-based extractors / responses for the Axum web framework. It also offers a

GengTeng 3 Dec 12, 2023
JWT Authentication in Rust using Axum Framework

Are you interested in building a secure authentication system for your Rust web application? Look no further than the Axum framework and JSON Web Tokens (JWTs)! Axum is a fast and scalable Rust web framework that provides a reliable and efficient platform for developing microservices and APIs.

CODEVO 16 Jun 11, 2023
Axum + JWT authentication Middleware that allows you to start building your application fast

axum_jwt_ware Integration Guide Simple Axum + JWT authentication middleware with implemented Login and refresh token. Goal I aim to simplify the proce

Eze Sunday 3 Dec 2, 2023
Quick demo of a REST frontend with a Redis session store.

axum-rest-starter-example Important Tasks Ensure session UUID is unique Protect /api/ with JWT Add CSRF CORS? Dev Setup (1) Run docker compose up to f

Michael de Silva 23 Dec 31, 2022
Layers, extractors and template engine wrappers for axum based Web MVC applications

axum-template Layers, extractors and template engine wrappers for axum based Web MVC applications Getting started Cargo.toml [dependencies] axum-templ

Altair Bueno 11 Dec 15, 2022
A Rust GraphQL system with full support for subscriptions and authentication that works out of the box.

Diana is a GraphQL system for Rust that's designed to work as simply as possible out of the box, without sacrificing configuration ability.

arctic_hen7 36 Dec 19, 2022
A Rust crate for managing authentication and authorization with support for multi-tenant / B2B products, powered by PropelAuth

PropelAuth Add authentication and authorization to your application. This library is meant to be used with a PropelAuth account. You can sign up and g

PropelAuth 3 Dec 10, 2022
A simple authentication flow using Rust and Actix-web, with a PostgreSQL database and a sveltekit frontend.

Rust-auth-example This repository aims to represent a simple authentication flow using Rust and Actix-web, with a PostgreSQL database and a sveltekit

Kival Mahadew 4 Feb 19, 2023
Experiments with Rust CRDTs using Tokio web application framework Axum.

crdt-genome Synopsis Experiments with Rust CRDTs using Tokio web application framework Axum. Background Exploring some ideas of Martin Kleppmann, part

dougfort 3 Mar 18, 2022
Axum web framework tutorial for beginners.

Axum Tutorial For Beginners Hello web developers! This tutorial will cover how to write simple web applications in rust with axum framework. If you ar

Eray Karatay 46 Jan 5, 2023
Yew + Axum + blog = Yab

Yew + Axum + blog = Yab

STUDIO RSBM 13 Dec 5, 2022
Demo of Rust and axum web framework

Demo of Rust and axum web framework Demonstration of: Rust: programming language that focuses on reliability and stability. axum: web framework that f

Joel Parker Henderson 115 Dec 29, 2022
Rust Axum+SQLx Sample

rust-axum-sqlx-sample Install git clone https://github.com/web3ten0/rust-axum-sqlx-1.git cd rust-axum-sqlx-1/local docker-compose up -d sh scripts/exe

web3ten0 5 Jul 9, 2022
🔎 Prometheus metrics middleware for Axum

Axum-Prometheus A Prometheus middleware to collect HTTP metrics for Axum applications. axum-prometheus relies on metrics_exporter_prometheus as a back

Péter Leéh 14 Jan 4, 2023
Provides json/csv/protobuf streaming support for axum

axum streams for Rust Library provides HTTP response streaming support for axum web framework: JSON array stream format JSON lines stream format CSV s

Abdulla Abdurakhmanov 15 Dec 11, 2022
Simple example of axum, sqlx with sqlite and utoipa (swagger) - without auth

axum_crud_api Simple example to learn creating CRUD rest apis in Rust with axum, sqlx with sqlite and utoipa (swagger) - without auth Also shows how t

null 2 Nov 12, 2022
Heavy Metal Leptos Stack with Tailwind, Axum, Sqlite, and Cargo Leptos

Heavy Metal Stack Leptos stack with Axum, TailwindCSS, and Sqlite This example creates a basic todo app with an Axum backend that uses Leptos' server

Ben Wishovich 7 Dec 31, 2022
A crate built on top of `axum-sessions`, implementing the CSRF Synchronizer Token Pattern

Axum Synchronizer Token Pattern CSRF prevention This crate provides a Cross-Site Request Forgery protection layer and middleware for use with the axum

null 3 Dec 15, 2022
Rate Limiting middleware for Tower/Axum/Tonic/Hyper utilizing the governor crate

A Tower service and layer that provides a rate-limiting backend by governor. Based heavily on the work done for actix-governor. Works with Axum, Hyper

Ben Wishovich 31 Feb 15, 2023