libauth-rs

Crates.iolibauth-rs
lib.rslibauth-rs
version0.1.2
created_at2025-10-17 15:59:27.519255+00
updated_at2025-12-17 16:06:41.464239+00
descriptionUnified authentication and authorization library with Stytch, Clerk, and MSAL support
homepagehttps://github.com/your-org/libauth-rs
repositoryhttps://github.com/your-org/libauth-rs
max_upload_size
id1887848
size262,172
Paul Liljenberg (pr0xr)

documentation

README

libauth-rs

A unified authentication library for Rust with support for multiple auth providers (Clerk, Stytch, MSAL) and Axum middleware integration.

Features

  • Multiple Auth Providers: Support for Clerk, Stytch, and Microsoft Azure AD / MSAL
  • Feature-Gated: Only compile and include the providers you need
  • Axum Middleware: Drop-in authentication middleware for Axum web applications
  • Provider-Specific Authorization: Built-in support for role-based and permission-based authorization
  • Issuer-Based Routing: Automatically route JWT tokens to the correct provider based on issuer
  • Per-Provider Routers: Create separate Axum routers for each authentication provider
  • Type-Safe: Strongly typed authentication primitives
  • Async-First: Built on async/await for high performance
  • API-Agnostic: Works with any HTTP framework or API style (REST, GraphQL, etc.)

Installation

Add to your Cargo.toml:

[dependencies]
libauth-rs = { path = ".", features = ["clerk", "axum"] }

Feature Flags

  • authentication - Core authentication functionality
  • authorization - Authorization and permissions (WIP)
  • clerk - Clerk authentication provider
  • stytch - Stytch authentication provider
  • msal - Microsoft Azure AD / MSAL authentication provider
  • axum - Axum middleware and extractors
  • all-providers - Enable all authentication providers

Features not yet implemented

  • scim - SCIM support

Usage

With Axum Middleware

use axum::{Router, routing::get, response::IntoResponse};
use libauth_rs::prelude::*;

#[tokio::main]
async fn main() {
    // Configure your auth provider
    let config = AuthConfig::default(); // Loads from environment variables
    let clerk = ClerkProvider::new(&config).await.unwrap();

    // Create the auth layer
    let auth_layer = AuthLayer::new(vec![Box::new(clerk)])
        .required(false); // Set to true to require authentication

    // Build your router
    let app = Router::new()
        .route("/", get(public_handler))
        .route("/protected", get(protected_handler))
        .layer(auth_layer);

    // Run your server
    let listener = tokio::net::TcpListener::bind("0.0.0.0:3000")
        .await
        .unwrap();
    axum::serve(listener, app).await.unwrap();
}

// Public handler - authentication is optional
async fn public_handler(
    OptionalAuth(user): OptionalAuth,
) -> impl IntoResponse {
    match user {
        Some(user) => format!("Hello, {}!", user.user_id),
        None => "Hello, anonymous!".to_string(),
    }
}

// Protected handler - authentication is required
async fn protected_handler(
    AuthExtension(user): AuthExtension,
) -> impl IntoResponse {
    format!("Welcome, {}! Your email is: {:?}", user.user_id, user.email)
}

Environment Variables

Clerk

CLERK_SECRET_KEY=sk_test_...
CLERK_PUBLISHABLE_KEY=pk_test_...

Stytch

STYTCH_PROJECT_ID=project-test-...
STYTCH_SECRET=secret-test-...

MSAL (Azure AD)

AZURE_CLIENT_ID=your-client-id
AZURE_CLIENT_SECRET=your-client-secret
AZURE_TENANT_ID=your-tenant-id

Multiple Providers

You can configure multiple providers and the library will automatically route tokens to the correct provider:

let config = AuthConfig::default();

let mut providers: Vec<Box<dyn AuthProvider>> = vec![];

// Add Clerk
if let Ok(clerk) = ClerkProvider::new(&config).await {
    providers.push(Box::new(clerk));
}

// Add Stytch
if let Ok(stytch) = StytchProvider::new(&config).await {
    providers.push(Box::new(stytch));
}

// Add MSAL
if let Ok(msal) = MsalProvider::new(&config).await {
    providers.push(Box::new(msal));
}

let auth_layer = AuthLayer::new(providers);

Without Axum (Manual Usage)

You can also use the providers directly without the middleware:

use libauth_rs::prelude::*;

#[tokio::main]
async fn main() {
    let config = AuthConfig::default();
    let clerk = ClerkProvider::new(&config).await.unwrap();

    // Create a token from an authorization header
    let token = AuthToken::bearer("sess_abc123...");

    // Authenticate
    match clerk.authenticate(&token).await {
        Ok(user) => {
            println!("Authenticated user: {}", user.user_id);
            println!("Email: {:?}", user.email);
            println!("Provider: {}", user.provider);
        }
        Err(e) => {
            eprintln!("Authentication failed: {}", e);
        }
    }
}

User Context

The UserContext struct contains all the authenticated user information:

pub struct UserContext {
    pub user_id: String,
    pub email: Option<String>,
    pub name: Option<String>,
    pub provider: AuthProvider,
    pub session_id: Option<String>,
    pub expires_at: Option<DateTime<Utc>>,
    pub metadata: HashMap<String, serde_json::Value>,
}

Architecture

┌─────────────────────────────────────────┐
│           Your Axum App                 │
└─────────────────────────────────────────┘
                  │
                  ▼
┌─────────────────────────────────────────┐
│         AuthLayer (Middleware)          │
│  • Extracts Authorization header        │
│  • Routes to correct provider           │
│  • Injects UserContext into request     │
└─────────────────────────────────────────┘
                  │
        ┌─────────┴──────────┐
        ▼          ▼         ▼
    ┌────────┐ ┌────────┐ ┌────────┐
    │ Clerk  │ │Stytch  │ │ MSAL   │
    │Provider│ │Provider│ │Provider│
    └────────┘ └────────┘ └────────┘

Provider-Specific Authorization

libauth-rs supports provider-specific authorization, allowing each provider to implement its own authorization logic:

use libauth_rs::middleware::AuthContext;

async fn admin_handler(auth: AuthContext) -> Result<String, StatusCode> {
    // Provider-specific role check
    auth.require_role("admin").await?;

    // Provider-specific permission check
    if !auth.check_permission("resource:write").await.unwrap_or(false) {
        return Err(StatusCode::FORBIDDEN);
    }

    Ok("Admin access granted!".to_string())
}

See AUTHORIZATION.md for complete documentation on authorization features.

Per-Provider Routers

You can create separate Axum routers for each authentication provider:

// Clerk-specific routes
let clerk_router = Router::new()
    .route("/orgs/:org_id/members", get(clerk_handler))
    .layer(AuthLayer::new(vec![Box::new(clerk)]).required(true));

// MSAL-specific routes
let msal_router = Router::new()
    .route("/azure/groups", get(msal_handler))
    .layer(AuthLayer::new(vec![Box::new(msal)]).required(true));

// Combine routers
let app = Router::new()
    .nest("/clerk", clerk_router)
    .nest("/msal", msal_router);

API-Agnostic Design

This library is designed to be API-agnostic - it doesn't care whether you're building a REST API, GraphQL API, or any other type of service. The middleware simply:

  1. Extracts authentication tokens from HTTP headers
  2. Validates them with the appropriate provider based on JWT issuer
  3. Injects the user context and provider reference into the request

You can then use the AuthExtension, AuthContext, or OptionalAuth extractors in any handler, regardless of what kind of API you're building.

License

MIT

Contributing

Contributions welcome! Please open an issue or PR.

Commit count: 0

cargo fmt