axum-security 0.0.2

A security toolbox for the Axum library
Documentation
# axum-security

A security toolkit for Axum.

## Features

`axum-security` is modular -- enable only what you need.

| Feature | Description |
|---------|-------------|
| `cookie` | Server-side session management with pluggable stores |
| `jwt` | JWT authentication (header or cookie) |
| `basic-auth` | HTTP Basic Authentication |
| `oauth2` | OAuth 2.0 authorization code flow |
| `oidc` | OpenID Connect (auto-discovery, ID token verification) |
| `rbac` | Role-based access control |
| `pbac` | Policy-based access control |
| `headers` | Security response headers (CSP, HSTS, etc.) |

Additional features: `macros`, `tracing`.

Time crate integration: `jiff`, `chrono`, `time`.

## Installation

```sh
cargo add axum-security --features cookie,jwt
```

Pick the features you need. No features are enabled by default.

## Cookie sessions

The `cookie` feature provides server-side session management. Sessions are stored in a pluggable store (an in-memory store is included for development).

```rust
use axum_security::cookie::{CookieContext, CookieSession, MemStore, SameSite};

// Build the cookie service
let cookie_service = CookieContext::builder()
    .cookie(|c| {
        c.name("session")
            .max_age(Duration::from_hours(24))
            .secure()
            .http_only()
            .same_site(SameSite::Strict)
    })
    .dev_cookie(|c| c.name("dev-session"))
    .use_dev_cookie(cfg!(debug_assertions))
    .store(MemStore::new())
    .expires_max_age()
    .build::<User>();

let router = Router::new()
    .route("/me", get(authorized))
    .route("/login", get(login))
    .layer(cookie_service.clone())
    .with_state(cookie_service);
```

Create sessions via `CookieContext` and extract them with `CookieSession`:

```rust
async fn login(session: CookieContext<User>, /* ... */) -> impl IntoResponse {
    let cookie = session.create_session(user).await.unwrap();
    (Some(cookie), "Logged in")
}

async fn authorized(user: CookieSession<User>) -> Json<User> {
    Json(user.state)
}

// Use Option<CookieSession<User>> for optional authentication.
```

## JWT

The `jwt` feature adds JWT-based authentication. By default, tokens are read from the `Authorization` header.

```rust
use axum_security::jwt::{Jwt, JwtContext};

let jwt_service = JwtContext::builder()
    .jwt_secret("my-secret")
    .build::<AccessToken>();

let router = Router::new()
    .route("/me", get(authorized))
    .route("/login", get(login))
    .layer(jwt_service.clone())
    .with_state(jwt_service);
```

Encode tokens via `JwtContext` and extract them with `Jwt`:

```rust
async fn login(context: JwtContext<AccessToken>, /* ... */) -> Result<String, StatusCode> {
    context
        .encode_token(&token)
        .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)
}

async fn authorized(Jwt(token): Jwt<AccessToken>) -> Json<AccessToken> {
    Json(token)
}
```

## Basic auth

The `basic-auth` feature provides HTTP Basic Authentication via a `BasicAuthenticator` trait.

```rust
use axum_security::basic_auth::{BasicAuth, BasicAuthLayer, BasicAuthenticator};

struct MyAuth;

impl BasicAuthenticator for MyAuth {
    type User = User;
    type Error = StatusCode;

    async fn authenticate(
        &self,
        username: &str,
        password: &str,
    ) -> Result<Option<User>, StatusCode> {
        if username == "admin" && password == "secret" {
            Ok(Some(User { username: username.to_owned() }))
        } else {
            Ok(None)
        }
    }
}

let router = Router::new()
    .route("/hello", get(hello))
    .layer(BasicAuthLayer::new(MyAuth));
```

Extract with `BasicAuth<User>` or `Option<BasicAuth<User>>`:

```rust
async fn hello(BasicAuth(user): BasicAuth<User>) -> String {
    format!("Hello, {}!", user.username)
}
```

## OAuth 2.0

The `oauth2` feature handles the authorization code flow. Implement `OAuth2Handler` to process the token response after a successful login.

```rust
use axum_security::oauth2::{
    AfterLoginCookies, OAuth2Context, OAuth2Ext, OAuth2Handler, TokenResponse,
};

impl OAuth2Handler for LoginHandler {
    async fn after_login(
        &self,
        token_res: TokenResponse,
        cookies: &mut AfterLoginCookies<'_>,
    ) -> impl IntoResponse {
        // Fetch user info, create a session, add cookies
        cookies.add(session_cookie);
        Redirect::to("/")
    }
}

let oauth2_service = OAuth2Context::github()
    .client_id_env("CLIENT_ID")
    .client_secret_env("CLIENT_SECRET")
    .redirect_url("http://localhost:3000/redirect")
    .login_path("/login")
    .build(handler);

let router = Router::new()
    .route("/me", get(authorized))
    .layer(cookie_service)
    .with_oauth2(oauth2_service);
```

## OIDC

The `oidc` feature adds OpenID Connect with auto-discovery and ID token verification. PKCE and nonce replay protection are always enabled.

```rust
use axum_security::oidc::{
    AfterLoginCookies, OidcContext, OidcExt, OidcHandler, OidcTokenResponse,
};

impl OidcHandler for LoginHandler {
    async fn after_login(
        &self,
        token_res: OidcTokenResponse,
        cookies: &mut AfterLoginCookies<'_>,
    ) -> impl IntoResponse {
        let user = User {
            subject: token_res.claims.subject,
            email: token_res.claims.email,
            name: token_res.claims.name,
        };
        cookies.add(self.cookie_service.create_session(user).await.unwrap());
        Redirect::to("/")
    }
}

let oidc_context = OidcContext::google()
    .await?
    .client_id_env("GOOGLE_CLIENT_ID")
    .client_secret_env("GOOGLE_CLIENT_SECRET")
    .redirect_url("http://localhost:3000/auth/oidc/callback")
    .login_path("/login")
    .logout_path("/logout")
    .scopes(&["openid", "email", "profile"])
    .build(handler);

let router = Router::new()
    .route("/me", get(me))
    .layer(cookie_service)
    .with_oidc(oidc_context);
```

## RBAC

The `rbac` feature provides role-based access control. Implement the `RBAC` trait to tell the library how to extract roles from your user type.

```rust
use axum_security::rbac::RBAC;

impl RBAC for Role {
    type Resource = User;

    fn extract_roles(resource: &Self::Resource) -> impl IntoIterator<Item = &Self> {
        Some(&resource.role)
    }
}
```

Protect routes with the `#[requires]` macro (needs the `macros` feature):

```rust
#[axum_security::rbac::requires(Role::Admin)]
async fn admin_only(cookie: CookieSession<User>) -> String {
    format!("hi admin: {}", cookie.state.name)
}
```

Or use the `RBACExt` methods on routes: `.requires()`, `.requires_all()`, `.allows()`.

## PBAC

The `pbac` feature adds policy-based access control. Policies are composable -- combine them with `.and()`, `.or()`, and `.not()`.

```rust
use axum_security::pbac::{HasRole, PolicyRouterExt};

fn is_not_banned(u: &User) -> bool {
    !u.banned
}

let admin_policy = HasRole::new(Role::Admin).and(is_not_banned);

let mod_policy = HasRole::new(Role::Admin)
    .or(HasRole::new(Role::Mod))
    .and(is_not_banned);

let router = Router::new()
    .route("/admin", get(handler).with_policy(admin_policy))
    .route("/mod-area", get(handler).with_policy(mod_policy))
    .layer(cookie_service);
```

Any `Fn(&User) -> bool` works as a policy. Use `AsyncPolicy` or `AsyncFalliblePolicy` for async checks.

## Security headers

The `headers` feature adds a Tower layer that sets security response headers. Use `SecurityHeaders::recommended()` for sensible defaults, or apply individual headers.

```rust
use axum_security::headers::{CrossOriginOpenerPolicy, SecurityHeaders, XssProtection};

let security_layer = SecurityHeaders::recommended()
    .use_dev_headers(cfg!(debug_assertions))
    .add(XssProtection::ZERO);

let coop_layer = CrossOriginOpenerPolicy::SAME_ORIGIN;

let router = Router::new()
    .route("/", get(index))
    .layer(security_layer)
    .layer(coop_layer);
```

## Examples

See the [`examples/`](examples/) directory for complete, runnable examples of each feature.

## Roadmap

- CSP nonce support
- OIDC logout support
- Eager/lazy session loading

## License

This project is licensed under the [MIT license](https://github.com/joeydewaal/axum-security/blob/main/LICENSE).