Authkestra

Core Traits

Authkestra uses traits to define extension points. Implement these traits to customize authentication behavior or add new providers.

OAuthProvider Trait

The OAuthProvider trait defines the interface for OAuth2/OIDC providers. Implement this to add support for new identity providers.

authkestra-core/src/lib.rs

/// Trait for an OAuth2-compatible provider.
#[async_trait]
pub trait OAuthProvider: Send + Sync {
    /// Get the provider identifier.
    fn provider_id(&self) -> &str;

    /// Helper to get the authorization URL.
    fn get_authorization_url(
        &self,
        state: &str,
        scopes: &[&str],
        code_challenge: Option<&str>,
    ) -> String;

    /// Exchange an authorization code for an Identity.
    async fn exchange_code_for_identity(
        &self,
        code: &str,
        code_verifier: Option<&str>,
    ) -> Result<(Identity, OAuthToken), AuthError>;

    /// Refresh an access token using a refresh token.
    async fn refresh_token(&self, _refresh_token: &str) -> Result<OAuthToken, AuthError> {
        Err(AuthError::Provider(
            "Token refresh not supported by this provider".into(),
        ))
    }

    /// Revoke an access token.
    async fn revoke_token(&self, _token: &str) -> Result<(), AuthError> {
        Err(AuthError::Provider(
            "Token revocation not supported by this provider".into(),
        ))
    }
}

Built-in Providers

Authkestra includes implementations for GitHub, Google, and Discord. You can use these directly or as reference when implementing custom providers.

CredentialsProvider Trait

For username/password or other direct authentication methods, implement the CredentialsProvider trait:

example.rs
use async_trait::async_trait;
use authkestra_core::{error::AuthError, state::Identity, CredentialsProvider};
use serde::Deserialize;
use std::collections::HashMap;

// 1. Define your credentials structure
#[derive(Deserialize)]
pub struct LoginCredentials {
    pub username: String,
    pub password: String,
}

// 2. Implement the CredentialsProvider trait
pub struct MyCredentialsProvider;

#[async_trait]
impl CredentialsProvider for MyCredentialsProvider {
    type Credentials = LoginCredentials;

    async fn authenticate(&self, creds: Self::Credentials) -> Result<Identity, AuthError> {
        // In production: verify against your database with proper password hashing!
        if creds.username == "admin" && creds.password == "secret" {
            Ok(Identity {
                provider_id: "credentials".to_string(),
                external_id: "user-123".to_string(),
                email: Some("admin@example.com".to_string()),
                username: Some("admin".to_string()),
                attributes: HashMap::new(),
            })
        } else {
            Err(AuthError::InvalidCredentials)
        }
    }
}

Password Security

Always use proper password hashing (bcrypt, argon2) in production. Never store or compare plain-text passwords.

UserMapper Trait

The UserMapper trait transforms an Identity into your application's local user type:

example.rs
use async_trait::async_trait;
use authkestra_core::{error::AuthError, state::Identity, UserMapper};

// Your application's user type
#[derive(Debug, Clone)]
pub struct LocalUser {
    pub id: i64,
    pub username: String,
    pub role: String,
    pub created_at: chrono::DateTime<chrono::Utc>,
}

pub struct DatabaseUserMapper {
    pub pool: sqlx::PgPool,
}

#[async_trait]
impl UserMapper for DatabaseUserMapper {
    type LocalUser = LocalUser;

    async fn map_user(&self, identity: &Identity) -> Result<Self::LocalUser, AuthError> {
        // Find or create user in database
        let user = sqlx::query_as!(
            LocalUser,
            r#"
            INSERT INTO users (external_id, provider, username, email)
            VALUES ($1, $2, $3, $4)
            ON CONFLICT (external_id, provider)
            DO UPDATE SET last_login = NOW()
            RETURNING id, username, role, created_at
            "#,
            identity.external_id,
            identity.provider_id,
            identity.username,
            identity.email
        )
        .fetch_one(&self.pool)
        .await
        .map_err(|e| AuthError::Internal(e.to_string()))?;

        Ok(user)
    }
}

ErasedOAuthFlow Trait

The ErasedOAuthFlow trait provides a type-erased interface for orchestrating the OAuth2 Authorization Code flow. This is particularly useful when you need to handle multiple different OAuth providers dynamically at runtime.

authkestra-core/src/lib.rs
#[async_trait]
pub trait ErasedOAuthFlow: Send + Sync {
    /// Get the provider identifier.
    fn provider_id(&self) -> String;
    
    /// Generates the redirect URL and CSRF state.
    fn initiate_login(
        &self,
        scopes: &[&str],
        pkce_challenge: Option<&str>
    ) -> (String, String);
    
    /// Completes the flow by exchanging the code.
    async fn finalize_login(
        &self,
        code: &str,
        received_state: &str,
        expected_state: &str,
        pkce_verifier: Option<&str>,
    ) -> Result<(Identity, OAuthToken), AuthError>;
}

ErasedOAuthFlow is implemented for Arc<T> and Box<T> where T: ErasedOAuthFlow, allowing for easy dynamic dispatch.

On this page