Skip to content

Stateless OAuth2

By default, OAuth2 logins can create a server-side session. However, Authkestra allows you to build a strictly stateless flow where authentication results in a signed JWT (JSON Web Token).

“Stateless” means that the entire flow can be completed without using a session store.

Authkestra strictly implements the OAuth 2.0 Authorization Framework (RFC 6749) and PKCE (RFC 7636). We provide a memory-safe, idiomatic Rust engine to execute them.

If you are using multiple OAuth providers (e.g., GitHub and Google), enable their respective feature flags:

[dependencies]
authkestra = { version = "0.9", features = ["axum", "token", "github", "google"] }

Notice how we completely omit .session_store() and use .jwt_secret() instead. You can chain multiple providers, each wrapped in an OAuth2Flow:

use authkestra::Authkestra;
use authkestra::providers::github::GithubProvider;
use authkestra::providers::google::GoogleProvider;
use authkestra_engine::OAuth2Flow;
// Initialize Authkestra in stateless mode supporting multiple providers
let auth_engine = Authkestra::builder()
.provider(OAuth2Flow::new(GithubProvider::new(
"GITHUB_CLIENT_ID".into(),
"GITHUB_CLIENT_SECRET".into(),
"http://localhost:3000/auth/callback/github".into(),
)))
.provider(OAuth2Flow::new(GoogleProvider::new(
"GOOGLE_CLIENT_ID".into(),
"GOOGLE_CLIENT_SECRET".into(),
"http://localhost:3000/auth/callback/google".into(),
)))
.jwt_secret(b"your-256-bit-secret-key-at-least-32-bytes-long")
.build();

A runnable version of this setup lives in the repository:

Terminal window
cargo run -p authkestra --example axum_oauth_stateless --all-features

Because we are not using sessions, we use the stateless router counterpart: auth_engine.axum_router_stateless() for Axum or auth_engine.actix_scope_stateless() for Actix-web. These live on AxumStatelessExt / ActixStatelessExt, not on AxumExt / ActixExt — importing the wrong trait is the usual cause of “no method named axum_router_stateless found”.

use authkestra::axum::{AxumState, AxumStatelessExt};
use authkestra::core::AkApiEngine;
// The router's state must carry the token-configured engine; the derive generates the
// `FromRef` impls `axum_router_stateless()` requires. A bare `Router<()>` will not compile.
#[derive(Clone, AxumState)]
struct AppState {
#[authkestra(engine)]
auth: AkApiEngine,
}
let state = AppState { auth: auth_engine.clone() };
let app: axum::Router = axum::Router::new()
.merge(auth_engine.axum_router_stateless())
.with_state(state);

If you need absolute control over the HTTP response, you can bypass the automatic router and handle requests manually using Authkestra’s helpers in authkestra-axum or authkestra-actix.

use axum::{extract::{Path, State, Query}, response::IntoResponse};
use authkestra::axum::helpers;
async fn login_handler(
Path(provider): Path<String>,
State(state): State<AppState>,
Query(params): Query<helpers::OAuthLoginParams>,
cookies: tower_cookies::Cookies,
) -> impl IntoResponse {
helpers::axum_login_handler::<AppState, _, _>(Path(provider), State(state), Query(params), cookies).await
}

By manually exposing these handlers, you gain full control over the HTTP response. You can append headers, record analytics, or return the JWT inside a custom JSON body.