Skip to content

Framework Integration

Authkestra provides first-class support for both the axum and actix-web web frameworks through lightweight adapter crates (authkestra-axum and authkestra-actix).

Ensure you have enabled the correct web framework feature on the authkestra crate in your Cargo.toml.

[dependencies]
authkestra = { version = "0.9", features = ["axum", "session"] }

Both extractors below read what they need out of your router state, not out of a global. On Axum that is a compile-time requirement: the AxumState derive (see Router Setup) generates the FromRef impls, and it only generates the ones your engine’s typestate actually has. So the engine alias you store in state decides which extractor compiles:

Engine alias .session_store() .jwt_secret() / .token_manager() AuthSession AuthToken
AkWebAppEngine ❌ compile error
AkApiEngine ❌ compile error
AkEngine

Using AuthToken with an AkWebAppEngine state fails with the trait bound Result<Arc<TokenManager>, AxumError>: FromRef<AppState> is not satisfied — the fix is to build the engine with a token manager and store it as AkApiEngine or AkEngine, not to change the handler.

On Actix the same requirement exists but is resolved at runtime from app_data: a handler taking Option<AuthToken> compiles against any state and simply always yields None when no TokenManager was registered.

The primary way to interact with an authenticated user in a handler when using sessions is via the AuthSession extractor. It automatically validates the session from cookies and injects the user’s identity.

use authkestra::axum::{AuthSession, AxumError};
use axum::response::{IntoResponse, Json};
use serde_json::json;
pub async fn my_handler(
session: Result<AuthSession, AxumError>
) -> impl IntoResponse {
match session {
Ok(AuthSession(session_data)) => Json(json!({
"message": "Welcome!",
"user_id": session_data.identity.external_id,
})),
Err(_) => Json(json!({ "error": "Not authenticated" }))
}
}

For stateless JWT authentication, Authkestra provides the AuthToken extractor which reads and validates the Authorization: Bearer <token> header.

use authkestra::axum::{AuthToken, AxumError};
use axum::response::{IntoResponse, Json};
use serde_json::json;
pub async fn token_protected_handler(
token: Result<AuthToken, AxumError>
) -> impl IntoResponse {
match token {
Ok(AuthToken(claims)) => Json(json!({
"message": "Valid Token!",
"sub": claims.sub,
})),
Err(_) => Json(json!({ "error": "Invalid or missing token" }))
}
}

To wire up standard authentication endpoints automatically, use axum_router() / axum_router_stateless() for Axum, or actix_scope() / actix_scope_stateless() for Actix-web on the built Engine. The Axum methods return an axum::Router; the Actix methods return an actix_web::Scope you mount with .service(...).

use authkestra::axum::{AxumExt, AxumState};
use authkestra::core::AkWebAppEngine;
use axum::Router;
use tower_cookies::CookieManagerLayer;
#[derive(Clone, AxumState)]
struct AppState {
#[authkestra(engine)]
auth: AkWebAppEngine,
}
let state = AppState { auth: auth_engine.clone() };
let app: Router = Router::new()
.merge(auth_engine.axum_router())
.layer(CookieManagerLayer::new())
.with_state(state);

.with_state(state) is not optional decoration: axum_router() returns a Router<AppState>, and the extractors resolve their dependencies out of that AppState. A Router<()> will not compile.