atproto-oauth-axum 0.5.0

Axum web framework integration for AT Protocol OAuth workflows
Documentation

atproto-oauth-axum

A Rust library providing complete Axum web handlers for AT Protocol OAuth 2.0 authorization server endpoints, including client metadata, JWKS, authorization callback handling, and a comprehensive command-line OAuth login tool.

Overview

atproto-oauth-axum provides ready-to-use Axum web handlers that implement the complete AT Protocol OAuth 2.0 authorization server specification. This library handles OAuth client metadata discovery, JSON Web Key Set (JWKS) endpoints, authorization callback processing, and includes a full-featured command-line tool for OAuth login flows.

This project was extracted from the open-sourced Smokesignal project and is designed to be a standalone, reusable library for AT Protocol OAuth server implementations.

Features

  • Complete OAuth Server Handlers: Ready-to-use Axum handlers for all required OAuth 2.0 endpoints
  • Client Metadata Endpoint: RFC 7591 compliant client metadata for dynamic client registration
  • JWKS Endpoint: JSON Web Key Set serving for JWT signature verification
  • Authorization Callback Handler: Complete OAuth callback processing with token exchange
  • OAuth Login CLI Tool: Full-featured command-line tool for testing and development OAuth flows
  • Axum Integration: Native Axum state management and request extractors
  • Error Handling: Comprehensive structured error types with proper HTTP responses
  • AT Protocol Compliance: Implements all AT Protocol-specific OAuth requirements

Installation

Add this to your Cargo.toml:

[dependencies]
atproto-oauth-axum = "0.5.0"

Usage

Basic Axum Server Setup

use atproto_oauth_axum::{
    handle_complete::handle_oauth_callback,
    handle_jwks::handle_oauth_jwks,
    handler_metadata::handle_oauth_metadata,
    state::OAuthClientConfig,
};
use axum::{routing::get, Router};
use atproto_identity::key::identify_key;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    // Set up OAuth client configuration
    let oauth_config = OAuthClientConfig {
        client_uri: "https://your-app.com".to_string(),
        client_id: "https://your-app.com/oauth/client-metadata.json".to_string(),
        redirect_uris: "https://your-app.com/oauth/callback".to_string(),
        jwks_uri: "https://your-app.com/.well-known/jwks.json".to_string(),
        signing_keys: vec![
            identify_key("did:key:zQ3shNzMp4oaaQ1gQRzCxMGXFrSW3NEM1M9T6KCY9eA7HhyEA")?
        ],
    };

    // Create Axum router with OAuth handlers
    let app = Router::new()
        .route("/oauth/client-metadata.json", get(handle_oauth_metadata))
        .route("/.well-known/jwks.json", get(handle_oauth_jwks))
        .route("/oauth/callback", get(handle_oauth_callback))
        .with_state(oauth_config);

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

    Ok(())
}

OAuth Client Metadata Handler

use atproto_oauth_axum::{handler_metadata::handle_oauth_metadata, state::OAuthClientConfig};
use axum::{routing::get, Router};

// The metadata handler automatically generates RFC 7591 compliant client metadata
let app = Router::new()
    .route("/oauth/client-metadata.json", get(handle_oauth_metadata))
    .with_state(oauth_config);

// Returns JSON like:
// {
//   "client_id": "https://your-app.com/oauth/client-metadata.json",
//   "client_uri": "https://your-app.com",
//   "dpop_bound_access_tokens": true,
//   "application_type": "web",
//   "redirect_uris": ["https://your-app.com/oauth/callback"],
//   "grant_types": ["authorization_code", "refresh_token"],
//   "response_types": ["code"],
//   "scope": "atproto transition:generic",
//   "token_endpoint_auth_method": "private_key_jwt",
//   "jwks_uri": "https://your-app.com/.well-known/jwks.json"
// }

JWKS Endpoint Handler

use atproto_oauth_axum::{handle_jwks::handle_oauth_jwks, state::OAuthClientConfig};
use axum::{routing::get, Router};

// The JWKS handler automatically converts your signing keys to JWK format
let app = Router::new()
    .route("/.well-known/jwks.json", get(handle_oauth_jwks))
    .with_state(oauth_config);

// Returns JSON Web Key Set with your public keys for signature verification

OAuth Callback Handler

use atproto_oauth_axum::{
    handle_complete::handle_oauth_callback,
    state::{OAuthClientConfig, HttpClient},
};
use atproto_identity::axum::state::{DidDocumentStorageExtractor, KeyProviderExtractor};
use atproto_oauth::axum::state::OAuthRequestStorageExtractor;
use axum::{routing::get, Router};

// The callback handler processes OAuth authorization callbacks
// It automatically:
// - Validates OAuth state parameters
// - Exchanges authorization codes for tokens
// - Validates DPoP proofs
// - Returns complete OAuth response with tokens

let app = Router::new()
    .route("/oauth/callback", get(handle_oauth_callback))
    .with_state(web_context); // Includes all required state

Integration with Other Libraries

use atproto_oauth_axum::state::{OAuthClientConfig, HttpClient};
use atproto_identity::{
    axum::state::{DidDocumentStorageExtractor, KeyProviderExtractor},
    storage_lru::LruDidDocumentStorage,
    key::KeyProvider,
};
use atproto_oauth::{
    axum::state::OAuthRequestStorageExtractor,
    storage_lru::LruOAuthRequestStorage,
};
use std::{num::NonZeroUsize, sync::Arc};

// Set up storage and state for full OAuth server
let did_storage = Arc::new(LruDidDocumentStorage::new(NonZeroUsize::new(256).unwrap()));
let oauth_storage = Arc::new(LruOAuthRequestStorage::new(NonZeroUsize::new(256).unwrap()));
let key_provider = Arc::new(your_key_provider_impl);

// The handlers automatically extract these from your application state

Command Line Tools

The crate includes a comprehensive command-line tool for OAuth operations:

atproto-oauth-tool

A complete OAuth login CLI tool that implements the full AT Protocol OAuth client flow. This tool sets up a local web server to handle OAuth callbacks and guides users through the complete authorization process from subject resolution to token acquisition.

Features:

  • Subject Resolution: Automatically resolves AT Protocol handles or DIDs to their identity documents
  • DID Document Retrieval: Fetches and validates DID documents from PLC directory or Web DID endpoints
  • PDS Discovery: Discovers Personal Data Server (PDS) endpoints from DID documents
  • Authorization Server Discovery: Retrieves OAuth authorization server metadata from PDS resources
  • PKCE Implementation: Generates secure PKCE parameters for authorization code flows
  • DPoP Key Generation: Creates DPoP keys for bound access tokens
  • Local OAuth Server: Runs temporary web server to handle authorization callbacks
  • Complete Token Exchange: Handles full OAuth flow from authorization to token acquisition
# Start OAuth login flow for a handle
cargo run --bin atproto-oauth-tool login did:key:zQ3sh... alice.bsky.social

# Start OAuth login flow for a DID
cargo run --bin atproto-oauth-tool login did:key:zQ3sh... did:plc:user123

# The tool will:
# 1. Resolve the subject to a DID
# 2. Fetch the DID document
# 3. Discover the PDS endpoint
# 4. Get OAuth authorization server configuration
# 5. Generate PKCE and DPoP parameters
# 6. Start a local server on http://localhost:8080
# 7. Display the authorization URL to visit
# 8. Handle the OAuth callback
# 9. Exchange authorization code for tokens
# 10. Display the complete OAuth response including access tokens and DPoP key

# Example output:
# OAuth server started on http://0.0.0.0:8080
# 🔐 OAuth Authorization URL:
# https://auth.bsky.social/oauth/authorize?client_id=https://localhost:8080/oauth/client-metadata.json&request_uri=urn:ietf:params:oauth:request_uri:abc123
# 
# Please visit this URL in your browser to complete the OAuth flow.
# The callback will be handled at: https://localhost:8080/oauth/callback

Server Endpoints: The tool automatically sets up these endpoints during the OAuth flow:

  • GET /oauth/client-metadata.json - OAuth client metadata
  • GET /.well-known/jwks.json - JSON Web Key Set
  • GET /oauth/callback - Authorization callback handler

Environment Variables:

# Required: Your application's external base URL
export EXTERNAL_BASE=your-app.com

# Optional: Custom PLC directory
export PLC_HOSTNAME=plc.directory

# Optional: Custom DNS nameservers
export DNS_NAMESERVERS=8.8.8.8;1.1.1.1

# Optional: Custom CA certificates
export CERTIFICATE_BUNDLES=/path/to/cert.pem

# Optional: Custom User-Agent
export USER_AGENT="my-oauth-client/1.0"

Security Features:

  • Cryptographically secure PKCE code verifier generation
  • DPoP proof-of-possession for bound access tokens
  • State parameter validation for CSRF protection
  • Automatic nonce handling for DPoP challenges
  • Private key security with no key storage

Modules

  • [handle_complete] - OAuth authorization callback handler with token exchange
  • [handler_metadata] - OAuth client metadata endpoint (RFC 7591)
  • [handle_jwks] - JSON Web Key Set endpoint for signature verification
  • [handle_init] - OAuth authorization initiation (reserved for future use)
  • [state] - Axum state management and request extractors
  • [errors] - Structured error types for OAuth operations

Error Handling

The crate uses comprehensive structured error types:

use atproto_oauth_axum::errors::{OAuthCallbackError, OAuthLoginError};

// OAuth callback handler errors
match callback_result {
    Err(OAuthCallbackError::NoOAuthRequestFound) => {
        println!("OAuth state not found - possible CSRF attack");
    },
    Err(OAuthCallbackError::InvalidIssuer { expected, actual }) => {
        println!("Issuer mismatch: expected {}, got {}", expected, actual);
    },
    Err(OAuthCallbackError::NoDIDDocumentFound) => {
        println!("DID document not found for OAuth request");
    },
    Ok(response) => println!("OAuth callback successful"),
}

// OAuth login CLI errors
match login_result {
    Err(OAuthLoginError::SubjectResolutionFailed { error }) => {
        println!("Failed to resolve subject: {}", error);
    },
    Err(OAuthLoginError::NoPDSEndpointFound) => {
        println!("No PDS endpoint found in DID document");
    },
    Err(OAuthLoginError::OAuthInitFailed { error }) => {
        println!("OAuth initialization failed: {}", error);
    },
    Ok(()) => println!("OAuth login completed successfully"),
}

Error Format

All errors follow the standardized format:

error-atproto-oauth-axum-<domain>-<number> <message>: <details>

Example error codes:

  • error-atproto-oauth-axum-callback-1 through error-atproto-oauth-axum-callback-7 - OAuth callback errors
  • error-atproto-oauth-axum-login-1 through error-atproto-oauth-axum-login-11 - OAuth login CLI errors

AT Protocol Compliance

This library implements all AT Protocol OAuth requirements:

OAuth Server Requirements

  • Support for authorization_code and refresh_token grant types
  • PKCE with S256 code challenge method
  • DPoP bound access tokens
  • private_key_jwt token endpoint authentication
  • ES256 signing algorithm support
  • Required OAuth scopes (atproto, transition:generic)

Client Requirements

  • Dynamic client registration metadata
  • JWKS endpoint for public key discovery
  • Proper redirect URI validation
  • State parameter CSRF protection

Dependencies

This crate builds on:

  • atproto-identity - Identity resolution and cryptographic operations
  • atproto-oauth - Core OAuth 2.0 operations and security extensions
  • axum - Web framework for HTTP handlers
  • reqwest - HTTP client for OAuth server communication
  • tokio - Async runtime for web server operations
  • serde_json - JSON serialization for OAuth responses
  • chrono - Date and time handling for OAuth flows
  • anyhow - Error handling utilities
  • thiserror - Structured error type derivation

Development and Testing

This crate is ideal for:

  • OAuth Server Development: Complete Axum handlers for AT Protocol OAuth servers
  • OAuth Client Testing: CLI tool for testing OAuth flows against AT Protocol services
  • Integration Testing: Ready-to-use handlers for OAuth endpoint testing
  • Development Workflows: Local OAuth server for development and debugging

Contributing

Contributions are welcome! Please ensure that:

  1. All tests pass: cargo test
  2. Code is properly formatted: cargo fmt
  3. No linting issues: cargo clippy
  4. New functionality includes appropriate tests and documentation
  5. Error handling follows the project's structured error format

License

This project is licensed under the MIT License. See the LICENSE file for details.

Acknowledgments

This library was extracted from the Smokesignal project, an open-source event and RSVP management and discovery application.