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.4.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 metadataGET /.well-known/jwks.json- JSON Web Key SetGET /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-1througherror-atproto-oauth-axum-callback-7- OAuth callback errorserror-atproto-oauth-axum-login-1througherror-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_codeandrefresh_tokengrant types - PKCE with
S256code challenge method - DPoP bound access tokens
private_key_jwttoken endpoint authenticationES256signing 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 operationsatproto-oauth- Core OAuth 2.0 operations and security extensionsaxum- Web framework for HTTP handlersreqwest- HTTP client for OAuth server communicationtokio- Async runtime for web server operationsserde_json- JSON serialization for OAuth responseschrono- Date and time handling for OAuth flowsanyhow- Error handling utilitiesthiserror- 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:
- All tests pass:
cargo test - Code is properly formatted:
cargo fmt - No linting issues:
cargo clippy - New functionality includes appropriate tests and documentation
- 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.