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);Integration with Custom State (Recommended: Using AuthkestraFromRef)
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 instanceResult<Arc<dyn SessionStore>, AuthkestraAxumError>- Required forAuthSessionextractorSessionConfig- Session configurationResult<Arc<TokenManager>, AuthkestraAxumError>- Required forAuthTokenextractor
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.
