Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Axum Integration API (oauth2-passkey-axum)

Overview

The oauth2-passkey-axum crate provides Axum web framework integration for the oauth2-passkey authentication library. It offers ready-to-use routers, middleware, and extractors for OAuth2 and WebAuthn/Passkey authentication.

Full API Documentation: https://docs.rs/oauth2-passkey-axum

Quick Start

use axum::{Router, response::Html};
use oauth2_passkey_axum::{oauth2_passkey_full_router, init};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Initialize authentication
    init().await?;

    // Create application router
    let app: Router = Router::new()
        .route("/", axum::routing::get(|| async { Html("Hello World!") }))
        // Add all authentication routes with a single call
        .merge(oauth2_passkey_full_router());

    // Start server
    let listener = tokio::net::TcpListener::bind("127.0.0.1:3000").await?;
    axum::serve(listener, app).await?;

    Ok(())
}

Routers

The unified router that provides all authentication endpoints. This is the recommended way to add authentication to your application.

#![allow(unused)]
fn main() {
use oauth2_passkey_axum::oauth2_passkey_full_router;

let app = Router::new()
    .merge(oauth2_passkey_full_router());
}

This router:

  • Nests all auth endpoints under O2P_ROUTE_PREFIX (default: /o2p)
  • Automatically includes /.well-known/webauthn when WEBAUTHN_ADDITIONAL_ORIGINS is configured
  • Handles single-origin and multi-origin setups seamlessly

Endpoints provided:

PathDescription
{O2P_ROUTE_PREFIX}/oauth2/...OAuth2 authentication endpoints
{O2P_ROUTE_PREFIX}/passkey/...WebAuthn/Passkey authentication endpoints
{O2P_ROUTE_PREFIX}/user/...User account management endpoints
{O2P_ROUTE_PREFIX}/admin/...Admin interface endpoints
/.well-known/webauthnWebAuthn relying party configuration (only when multi-origin is configured)

See Endpoint Reference for complete details.

oauth2_passkey_router

The auth-only router without the prefix nesting. Use this for custom setups where you need more control.

#![allow(unused)]
fn main() {
use oauth2_passkey_axum::{oauth2_passkey_router, O2P_ROUTE_PREFIX};

let app = Router::new()
    .nest(O2P_ROUTE_PREFIX.as_str(), oauth2_passkey_router());
}

Endpoints provided (relative to mount point):

PathDescription
/oauth2/...OAuth2 authentication endpoints
/passkey/...WebAuthn/Passkey authentication endpoints
/user/...User account management endpoints
/admin/...Admin interface endpoints

passkey_well_known_router

Router for the WebAuthn well-known endpoint. Only needed if you use oauth2_passkey_router() directly with a multi-origin setup.

#![allow(unused)]
fn main() {
use oauth2_passkey_axum::{oauth2_passkey_router, passkey_well_known_router, O2P_ROUTE_PREFIX};

let app = Router::new()
    .nest(O2P_ROUTE_PREFIX.as_str(), oauth2_passkey_router())
    .merge(passkey_well_known_router());
}

This creates a /.well-known/webauthn endpoint for WebAuthn relying party configuration. See Multi-Origin Passkey Setup for details.

Note: If you use oauth2_passkey_full_router(), this endpoint is included automatically when needed.

Middleware

Authentication middleware for protecting routes. All middleware functions:

  1. Verify valid session cookie
  2. For state-changing methods (POST, PUT, DELETE, PATCH), verify CSRF protection
  3. Add CSRF token to response headers

is_authenticated_401

Returns HTTP 401 Unauthorized for unauthenticated requests.

#![allow(unused)]
fn main() {
use axum::{Router, middleware::from_fn};
use oauth2_passkey_axum::is_authenticated_401;

let app: Router = Router::new()
    .route("/api/data", axum::routing::get(handler))
    .layer(from_fn(is_authenticated_401));
}

is_authenticated_redirect

Redirects unauthenticated GET requests to login page; returns 401 for other methods.

#![allow(unused)]
fn main() {
use axum::{Router, middleware::from_fn};
use oauth2_passkey_axum::is_authenticated_redirect;

let app: Router = Router::new()
    .route("/dashboard", axum::routing::get(handler))
    .layer(from_fn(is_authenticated_redirect));
}

is_authenticated_user_401

Like is_authenticated_401, but also extracts user data into an Extension<AuthUser>.

#![allow(unused)]
fn main() {
use axum::{Router, middleware::from_fn, extract::Extension};
use oauth2_passkey_axum::{is_authenticated_user_401, AuthUser};

async fn handler(Extension(user): Extension<AuthUser>) -> String {
    format!("Hello, {}", user.account)
}

let app: Router = Router::new()
    .route("/api/profile", axum::routing::get(handler))
    .layer(from_fn(is_authenticated_user_401));
}

is_authenticated_user_redirect

Like is_authenticated_redirect, but also extracts user data into an Extension<AuthUser>.

#![allow(unused)]
fn main() {
use axum::{Router, middleware::from_fn, extract::Extension};
use oauth2_passkey_axum::{is_authenticated_user_redirect, AuthUser};

async fn handler(Extension(user): Extension<AuthUser>) -> String {
    format!("Hello, {}", user.account)
}

let app: Router = Router::new()
    .route("/dashboard", axum::routing::get(handler))
    .layer(from_fn(is_authenticated_user_redirect));
}

Extractors

AuthUser

Axum extractor for authenticated user information. Automatically verifies session and CSRF tokens.

#![allow(unused)]
fn main() {
use axum::routing::get;
use oauth2_passkey_axum::AuthUser;

async fn protected_handler(user: AuthUser) -> String {
    format!("Hello, {}!", user.label)
}

let app: Router = Router::new()
    .route("/protected", get(protected_handler));
}

Fields:

FieldTypeDescription
idStringUnique user identifier
accountStringUser’s account name (email or username)
labelStringUser’s display name
is_adminboolWhether user has admin privileges
sequence_numberOption<i64>Database sequence number
created_atDateTime<Utc>Account creation timestamp
updated_atDateTime<Utc>Last update timestamp
csrf_tokenStringCSRF token for the session
csrf_via_header_verifiedboolWhether CSRF was verified via header
session_idStringSession ID for secure API calls

Methods:

  • has_admin_privileges() - Returns true if user has admin rights (either is_admin flag or is first user)

Optional Extraction:

AuthUser also implements OptionalFromRequestParts, allowing optional user extraction:

#![allow(unused)]
fn main() {
async fn handler(user: Option<AuthUser>) -> String {
    match user {
        Some(u) => format!("Hello, {}!", u.label),
        None => "Hello, guest!".to_string(),
    }
}
}

URL Constants

ConstantDescription
O2P_ROUTE_PREFIXRoute prefix for auth endpoints (default: /o2p)
O2P_LOGIN_URLLogin page URL
O2P_ADMIN_URLAdmin interface URL
O2P_ACCOUNT_URLUser account management page URL
O2P_DEFAULT_REDIRECTDefault redirect URL for auth flows

Re-exports from oauth2-passkey

The following are re-exported from the core library for convenience:

ItemDescription
initInitialize the authentication system
O2P_ROUTE_PREFIXRoute prefix constant
CsrfTokenCSRF token type
CsrfHeaderVerifiedCSRF header verification marker

Example: Protected API Routes

use axum::{Router, routing::{get, post}, middleware::from_fn, Json};
use oauth2_passkey_axum::{
    oauth2_passkey_full_router, is_authenticated_user_401, AuthUser, init
};
use serde::Serialize;

#[derive(Serialize)]
struct UserProfile {
    id: String,
    name: String,
    is_admin: bool,
}

async fn get_profile(user: AuthUser) -> Json<UserProfile> {
    Json(UserProfile {
        id: user.id,
        name: user.label,
        is_admin: user.has_admin_privileges(),
    })
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    init().await?;

    // Protected API routes
    let api_routes = Router::new()
        .route("/profile", get(get_profile))
        .layer(from_fn(is_authenticated_user_401));

    let app = Router::new()
        // Authentication routes (includes /.well-known/webauthn when needed)
        .merge(oauth2_passkey_full_router())
        // Protected API
        .nest("/api", api_routes);

    let listener = tokio::net::TcpListener::bind("127.0.0.1:3000").await?;
    axum::serve(listener, app).await?;

    Ok(())
}

Example: Protected Web Pages with Redirect

#![allow(unused)]
fn main() {
use axum::{Router, routing::get, middleware::from_fn, response::Html};
use oauth2_passkey_axum::{is_authenticated_user_redirect, AuthUser};

async fn dashboard(user: AuthUser) -> Html<String> {
    Html(format!(
        "<h1>Welcome, {}!</h1><p>Your account: {}</p>",
        user.label,
        user.account
    ))
}

let protected_pages = Router::new()
    .route("/dashboard", get(dashboard))
    .layer(from_fn(is_authenticated_user_redirect));
}

Endpoint Reference

Complete list of all endpoints provided by oauth2_passkey_router().

OAuth2 Endpoints (/oauth2)

EndpointMethodDescription
/googleGETStart Google OAuth2 authentication flow
/authorizedGET, POSTOAuth2 callback endpoint (redirect URI)
/popup_closeGETClose popup window after OAuth2 flow
/accountsGETList user’s linked OAuth2 accounts
/accounts/{provider}/{provider_user_id}DELETEUnlink an OAuth2 account
/oauth2.jsGETJavaScript SDK for OAuth2 operations

Passkey Endpoints (/passkey)

EndpointMethodDescription
/register/startPOSTStart passkey registration ceremony
/register/finishPOSTComplete passkey registration ceremony
/auth/startPOSTStart passkey authentication ceremony
/auth/finishPOSTComplete passkey authentication ceremony
/credentialsGETList user’s passkey credentials
/credentials/{credential_id}DELETEDelete a passkey credential
/credential/updatePOSTUpdate passkey credential metadata
/conditional_uiGETConditional UI page for passkey autofill*
/passkey.jsGETJavaScript SDK for passkey operations
/conditional_ui.jsGETJavaScript for conditional UI*

User Endpoints (/user)

EndpointMethodDescription
/loginGETLogin page*
/accountGETUser account management page*
/logoutGETEnd session and redirect
/infoGETGet current user info as JSON*
/csrf_tokenGETGet CSRF token for API calls*
/deleteDELETEDelete user account
/updatePUTUpdate user profile (account, label)
/login_historyGETUser’s own login history (JSON)
/login_history_pageGETLogin history page*
/account.jsGETJavaScript for account page*
/account.cssGETCSS for account page*
/o2p-base.cssGETBase CSS styles*

Admin Endpoints (/admin)

EndpointMethodDescription
/indexGETUser management list page*
/user/{user_id}GETUser detail page*
/usersGETList all users (JSON)
/delete_userDELETEDelete a user (admin only)
/delete_passkey_credential/{credential_id}DELETEDelete user’s passkey (admin only)
/delete_oauth2_account/{provider}/{provider_user_id}DELETEUnlink user’s OAuth2 account (admin only)
/update_admin_statusPUTGrant/revoke admin privileges
/sessionsGETActive session counts for all users (JSON)*
/user/{user_id}/logoutPOSTForce logout a user (terminate all sessions)*
/user/{user_id}/login_historyGETPer-user login history (JSON)
/auditGETCross-user audit data with filters (JSON)
/audit_pageGETAudit page HTML template
/admin_user.jsGETJavaScript for admin pages*
/admin_user.cssGETCSS for admin pages*

Login history query parameters (for /audit and /user/{user_id}/login_history):

ParameterTypeDefaultDescription
limitinteger50Maximum entries to return
offsetinteger0Pagination offset
fromstring-Filter from date (YYYY-MM-DD)
tostring-Filter to date (YYYY-MM-DD)
tz_offsetinteger0Timezone offset from UTC (minutes)
user_idstring-Filter by user (audit only)
successboolean-Filter by success/failure (audit only)

Admin safeguards: The admin API enforces protections to prevent admin lockout:

  • Cannot delete or demote the last remaining admin user
  • The first user (seq=1) cannot be demoted
  • Admin users cannot delete their own account via the admin interface

Theme Endpoints (/themes)

EndpointMethodDescription
/theme-zinc.cssGETZinc theme (neutral gray)
/theme-slate.cssGETSlate theme (cool gray)
/theme-blue.cssGETBlue theme
/theme-violet.cssGETViolet theme
/theme-rose.cssGETRose theme
/theme-neumorphism.cssGETNeumorphism style theme
/theme-material.cssGETMaterial Design theme
/theme-eco.cssGETEco / nature theme
/theme-saas.cssGETSaaS dashboard theme

These are optional CSS theme files that override o2p-base.css variables. Set O2P_CUSTOM_CSS_URL to apply a theme. See Themes for details.

Feature Flags

Endpoints marked with * are controlled by feature flags:

FeatureDefaultEndpoints Affected
login-uiON/user/login
user-uiON/user/account, /user/account.js, /user/o2p-base.css
admin-uiON/admin/index, /admin/user/{id}, /admin/sessions, /admin/user/{id}/logout, admin static files

API endpoints (/user/info, /user/csrf_token, /user/logout, /user/update, /user/delete, authentication, CRUD operations) are always available regardless of feature flags.

# Disable all built-in UI
oauth2-passkey-axum = { version = "0.3", default-features = false }

# Custom login page, keep account management and admin UI
oauth2-passkey-axum = { version = "0.3", default-features = false, features = ["user-ui", "admin-ui"] }

# Keep admin UI only
oauth2-passkey-axum = { version = "0.3", default-features = false, features = ["login-ui", "admin-ui"] }

Additional Router: passkey_well_known_router()

EndpointMethodDescription
/.well-known/webauthnGETWebAuthn relying party configuration

This router should be merged at the root level (not nested under O2P_ROUTE_PREFIX). Only needed for multi-origin setups. See Multi-Origin Passkey Setup.