Authkestra

Axum

Use authkestra-axum to integrate authentication into your Axum applications.

authkestra-axum provides a seamless integration for Axum applications, offering type-safe extractors, state management, and automatic route generation for OAuth providers.

Introduction

The Axum adapter simplifies the process of adding authentication to your web services. By leveraging Axum's powerful extractor pattern and state management, Authkestra allows you to protect routes and manage user sessions with minimal configuration.

State Management

Authkestra integrates with Axum's state system via AuthkestraState. This struct holds the configuration and provider registry required for authentication.

Using AuthkestraState Directly

If your application does not require any additional custom state, you can use AuthkestraState directly as your application state:

use authkestra_axum::{AuthkestraState, AuthkestraAxumExt};
use authkestra_flow::Authkestra;
use axum::Router;

// ... configure authkestra ...

let state = AuthkestraState::from(authkestra.clone());

let app = Router::new()
    .merge(authkestra.axum_router())
    .with_state(state);

If your application manages its own state, the easiest way to integrate Authkestra is using the AuthkestraFromRef derive macro. This macro automatically generates all the required FromRef trait implementations needed for Authkestra's extractors to work with your custom state.

Quick Start with AuthkestraFromRef

Add the macro feature to your dependencies:

[dependencies]
authkestra-axum = { version = "0.1", features = ["macros"] }

Then simply derive AuthkestraFromRef on your state struct and mark the Authkestra field with #[authkestra]:

use authkestra_axum::AuthkestraFromRef;
use authkestra_flow::{Authkestra, Configured, Missing};
use authkestra_session::SessionStore;
use std::sync::Arc;

#[derive(Clone, AuthkestraFromRef)]
struct AppState {
    #[authkestra]
    auth: Authkestra<Configured<Arc<dyn SessionStore>>, Missing>,
    db_pool: MyDbPool,
    // ... other application state
}

That's it! The macro automatically generates FromRef implementations for:

  • Authkestra<S, T> - The main Authkestra instance
  • Result<Arc<dyn SessionStore>, AuthkestraAxumError> - Required for AuthSession extractor
  • SessionConfig - Session configuration
  • Result<Arc<TokenManager>, AuthkestraAxumError> - Required for AuthToken extractor

Now you can use Authkestra extractors in your handlers without any additional boilerplate:

use authkestra_axum::AuthSession;

async fn profile(AuthSession(session): AuthSession) -> String {
    format!("Welcome, {}!", session.identity.username.unwrap_or_default())
}

fn app(state: AppState) -> Router {
    Router::new()
        .route("/profile", get(profile))
        .merge(state.auth.axum_router())
        .with_state(state)
}

Manual Integration (Alternative)

If you prefer more control or cannot use the macro, you can manually implement the FromRef trait. This is useful for custom configurations or advanced use cases.

use authkestra_flow::{Authkestra, SessionStoreState, TokenManagerState, Missing};
use authkestra_axum::AuthkestraAxumError;
use authkestra_session::{SessionStore, SessionConfig};
use authkestra_token::TokenManager;
use axum::extract::FromRef;
use std::sync::Arc;

#[derive(Clone)]
struct AppState<S = Missing, T = Missing> {
    pub db_pool: MyDbPool,
    /// Authkestra instance
    pub authkestra: Authkestra<S, T>,
}

// Implement FromRef for Authkestra
impl<S: Clone, T: Clone> FromRef<AppState<S, T>> for Authkestra<S, T> {
    fn from_ref(state: &AppState<S, T>) -> Self {
        state.authkestra.clone()
    }
}

// Implement FromRef for SessionStore (required for AuthSession)
impl<S, T> FromRef<AppState<S, T>> for Result<Arc<dyn SessionStore>, AuthkestraAxumError>
where
    S: SessionStoreState,
{
    fn from_ref(state: &AppState<S, T>) -> Self {
        Ok(state.authkestra.session_store.get_store())
    }
}

// Implement FromRef for SessionConfig (required for AuthSession)
impl<S, T> FromRef<AppState<S, T>> for SessionConfig {
    fn from_ref(state: &AppState<S, T>) -> Self {
        state.authkestra.session_config.clone()
    }
}

// Implement FromRef for TokenManager (required for AuthToken)
impl<S, T> FromRef<AppState<S, T>> for Result<Arc<TokenManager>, AuthkestraAxumError>
where
    T: TokenManagerState,
{
    fn from_ref(state: &AppState<S, T>) -> Self {
        Ok(state.authkestra.token_manager.get_manager())
    }
}

Extractors

Authkestra provides several extractors to simplify authentication in your handlers.

AuthSession

Used for session-based authentication.

use authkestra_axum::AuthSession;

async fn profile(AuthSession(session): AuthSession) -> String {
    format!("Welcome back, {}!", session.identity.username.unwrap_or_default())
}

AuthToken

Used for JWT-based authentication.

use authkestra_axum::AuthToken;

async fn api_handler(AuthToken(claims): AuthToken) -> String {
    format!("Hello user with ID: {}", claims.sub)
}

Jwt<T> (Offline Validation)

Used for validating tokens from external providers using JWKS.

use authkestra_axum::Jwt;
use serde::Deserialize;

#[derive(Deserialize)]
struct MyClaims {
    sub: String,
}

async fn external_api_handler(Jwt(claims): Jwt<MyClaims>) -> String {
    format!("Hello external user: {}", claims.sub)
}

Configuration with Typestates

Authkestra uses a typestate pattern to ensure that your application is correctly configured at compile time.

Session-based Application

use authkestra_flow::Authkestra;
use authkestra_session::MemoryStore;
use std::sync::Arc;

let session_store = Arc::new(MemoryStore::default());

let authkestra = Authkestra::builder()
    .session_store(session_store)
    .build();

Token-based Application (Stateless)

use authkestra_flow::Authkestra;

let authkestra = Authkestra::builder()
    .jwt_secret(b"your-32-byte-long-secret-key-here!!")
    .jwt_issuer("my-app")
    .build();

Router Integration

Instead of manually defining endpoints for every provider, you can use the axum_router() method to automatically register all necessary authentication routes.

Merging the Router

The axum_router() method returns an Axum Router that you can merge into your main application. This automatically handles login redirection, callbacks, and logout for all configured providers.

use authkestra_axum::AuthkestraAxumExt;

let app = Router::new()
    .route("/", get(index))
    .route("/protected", get(protected))
    // Merge all Authkestra routes automatically
    .merge(authkestra.axum_router()) 
    .with_state(state);

By default, this registers:

  • GET /auth/{provider}: Initiates the login flow for a specific provider.
  • GET /auth/{provider}/callback: Handles the OAuth2 callback.
  • GET /auth/logout: Clears the user session.

SPA Example: JWT Authentication

In a Single Page Application (SPA), you typically want the backend to exchange the OAuth code for a JWT that the frontend can store and use for subsequent requests.

1. Configure Authkestra

To issue JWTs, you must provide a TokenManager (or use the jwt_secret helper) to the Authkestra builder.

let authkestra = Authkestra::builder()
    .provider(OAuth2Flow::new(GithubProvider::new(client_id, client_secret, redirect_uri)))
    .jwt_secret(b"a-very-secret-key-that-is-at-least-32-bytes-long!!")
    .build();

2. Handle the OAuth Callback

In SPA mode, your callback handler uses handle_oauth_callback_jwt. This helper exchanges the OAuth code for an AccessTokenResponse containing your app's JWT.

use authkestra_axum::{handle_oauth_callback_jwt, OAuthCallbackParams};
use axum::{extract::{State, Query}, response::IntoResponse};

async fn callback_handler(
    State(state): State<AppState>,
    Query(params): Query<OAuthCallbackParams>,
    cookies: tower_cookies::Cookies,
) -> Result<impl IntoResponse, AuthkestraAxumError> {
    let flow = &state.authkestra.providers["github"];

    handle_oauth_callback_jwt(
        flow.as_ref(),
        cookies,
        params,
        state.authkestra.token_manager.get_manager(), // Access the configured manager
        3600,
    )
    .await
}

Conclusion

With Authkestra's Axum integration, you can move from a single-provider setup to a multi-provider system by simply adding more providers to the builder. The rest of your application remains clean and focused on your business logic.

For a complete working example, see the axum_credentials.rs file in the authkestra-examples directory on GitHub.

On this page