Authkestra

Authkestra Guard

Protecting your API with Authkestra Guard and Offline Token Validation.

Authkestra Guard

authkestra-guard is a high-level crate designed to protect your APIs by orchestrating multiple authentication strategies. It is particularly powerful for Offline Validation, allowing your services to verify JWTs issued by external providers (like Google, Auth0, or Microsoft Entra) without making a network call for every request.

Key Features

  • Multi-strategy Support: Chain multiple authentication methods (e.g., JWT, API Keys, Session).
  • Flexible Policies: Define how strategies interact (FirstSuccess, AllSuccess, FailFast).
  • Offline JWT Validation: Local verification of JWTs using JSON Web Key Sets (JWKS).
  • JWKS Caching: Efficient caching and automatic rotation of public keys.

Installation

Add authkestra-guard to your Cargo.toml:

[dependencies]
authkestra-guard = "0.1.0"
# You'll likely need these for defining claims
serde = { version = "1.0", features = ["derive"] }

Offline JWT Validation

Offline validation allows your application to verify the authenticity of a JWT locally. This is essential for high-performance resource servers.

1. Configure Validation

Use the ValidationConfig builder to specify your provider's JWKS endpoint and validation rules.

use authkestra_guard::jwt::ValidationConfig;
use std::time::Duration;
use jsonwebtoken::Algorithm;

let config = ValidationConfig::builder()
    .jwks_url("https://www.googleapis.com/oauth2/v3/certs")
    .refresh_interval(Duration::from_secs(3600)) // Refresh keys every hour
    .issuer("https://accounts.google.com")
    .audience("your-client-id")
    .algorithms(vec![Algorithm::RS256])
    .build();

2. Using JwtStrategy

The JwtStrategy implements the AuthenticationStrategy trait and can be used directly or within the AuthkestraGuard.

use authkestra_guard::jwt::{JwtStrategy, Claims};
use authkestra_guard::AuthkestraGuard;

// I is your Identity type, which must implement Deserialize
let jwt_strategy = JwtStrategy::<Claims>::new(config);

let guard = AuthkestraGuard::builder()
    .strategy(jwt_strategy)
    .build();

3. Validating a Request

The AuthkestraGuard works with http::request::Parts, making it compatible with most Rust web frameworks.

// In your middleware or handler
let parts = request.parts(); // Simplified
match guard.authenticate(parts).await {
    Ok(Some(claims)) => println!("Authenticated user: {:?}", claims.sub),
    Ok(None) => println!("No valid authentication found"),
    Err(e) => eprintln!("Authentication error: {}", e),
}

Multi-Strategy Guard

You can combine multiple strategies and define an AuthPolicy.

use authkestra_guard::{AuthkestraGuard, AuthPolicy};

let guard = AuthkestraGuard::builder()
    .strategy(jwt_strategy)
    // .strategy(api_key_strategy) // Assuming another strategy exists
    .policy(AuthPolicy::FirstSuccess)
    .build();
PolicyDescription
FirstSuccess(Default) Returns the first successful identity. Fails if any strategy returns an error.
AllSuccessRequires all strategies to succeed. Returns the identity from the last one.
FailFastOnly attempts the first strategy. If it fails or returns None, stops immediately.

Built-in Strategies

While authkestra-guard provides JwtStrategy for OIDC and JWT-based authentication, authkestra-core includes several other built-in strategies for common use cases.

BasicStrategy

The BasicStrategy handles standard HTTP Basic Authentication. It requires a type that implements the BasicAuthenticator trait.

use authkestra_core::strategy::{BasicAuthenticator, BasicStrategy};
use authkestra_core::error::AuthError;
use async_trait::async_trait;

struct MyAuthenticator;

#[async_trait]
impl BasicAuthenticator for MyAuthenticator {
    type Identity = User;

    async fn authenticate(&self, user: &str, pass: &str) -> Result<Option<User>, AuthError> {
        if user == "admin" && pass == "secret" {
            Ok(Some(User { id: "1".into(), username: "admin".into() }))
        } else {
            Err(AuthError::InvalidCredentials)
        }
    }
}

let strategy = BasicStrategy::new(MyAuthenticator);

TokenStrategy

The TokenStrategy validates Bearer tokens from the Authorization header. It requires a type that implements the TokenValidator trait.

use authkestra_core::strategy::{TokenValidator, TokenStrategy};

struct MyTokenValidator;

#[async_trait]
impl TokenValidator for MyTokenValidator {
    type Identity = User;

    async fn validate(&self, token: &str) -> Result<Option<User>, AuthError> {
        if token == "valid-token" {
            Ok(Some(User { id: "1".into(), username: "token_user".into() }))
        } else {
            Ok(None)
        }
    }
}

let strategy = TokenStrategy::new(MyTokenValidator);

HeaderStrategy

The HeaderStrategy allows you to validate a custom HTTP header using a closure or a function.

use authkestra_core::strategy::HeaderStrategy;
use http::header::HeaderName;

let header_name = HeaderName::from_static("x-api-key");
let strategy = HeaderStrategy::new(header_name, |key| async move {
    if key == "secret-key" {
        Ok(Some(User { id: "1".into(), username: "api_user".into() }))
    } else {
        Ok(None)
    }
});

SessionStrategy

The SessionStrategy extracts a session ID from a cookie and uses a SessionProvider to load the associated identity.

use authkestra_core::strategy::{SessionProvider, SessionStrategy};

struct MySessionProvider;

#[async_trait]
impl SessionProvider for MySessionProvider {
    type Identity = User;

    async fn load_session(&self, session_id: &str) -> Result<Option<User>, AuthError> {
        // Load user from database or cache using session_id
        Ok(Some(User { id: "1".into(), username: "session_user".into() }))
    }
}

let strategy = SessionStrategy::new(MySessionProvider, "session_id");

Custom Authentication Strategy

You can implement the AuthenticationStrategy trait to create your own authentication methods, such as custom headers, API keys, or specialized validation logic.

Implementing AuthenticationStrategy

The trait requires implementing the authenticate method, which receives the HTTP request Parts and returns an Option<Identity>.

use async_trait::async_trait;
use authkestra_core::error::AuthError;
use authkestra_core::strategy::AuthenticationStrategy;
use axum::http::request::Parts;

#[async_trait]
impl AuthenticationStrategy<User> for CustomHeaderStrategy {
    async fn authenticate(&self, parts: &Parts) -> Result<Option<User>, AuthError> {
        // Look for the X-API-Key header
        if let Some(value) = parts.headers.get("X-API-Key") {
            if let Ok(value_str) = value.to_str() {
                // Validate the header value
                if value_str == self.api_key {
                    return Ok(Some(User {
                        id: "1".to_string(),
                        username: "api_user".to_string(),
                    }));
                } else {
                    return Err(AuthError::InvalidCredentials);
                }
            }
        }

        // Return Ok(None) to allow other strategies in the chain to try
        Ok(None)
    }
}

Unified Auth Extractor

authkestra-axum and authkestra-actix provide a unified Auth<T> extractor that simplifies accessing the authenticated user in your route handlers.

Usage in Axum

To use the extractor, ensure your AuthkestraGuard is shared as state in your router.

use authkestra_axum::Auth;

async fn protected_route(Auth(user): Auth<User>) -> String {
    format!("Hello, {}! Your ID is {}.", user.username, user.id)
}

fn app(guard: Arc<AuthkestraGuard<User>>) -> Router {
    Router::new()
        .route("/protected", get(protected_route))
        .with_state(guard)
}

Low-level API

If you don't need the full Guard orchestration, you can use the lower-level validation functions:

use authkestra_guard::jwt::{validate_jwt, JwksCache};
use jsonwebtoken::Validation;

let cache = JwksCache::new(jwks_uri, Duration::from_secs(3600));
let validation = Validation::new(Algorithm::RS256);

let claims = validate_jwt(token, &cache, &validation).await?;

On this page