A library for ATProtocol identities.
README.md

atproto-oauth#

A Rust library for AT Protocol OAuth 2.0 operations, providing comprehensive support for OAuth security extensions, JWT operations, and AT Protocol-specific OAuth flows.

Overview#

atproto-oauth provides OAuth 2.0 functionality specifically designed for the AT Protocol ecosystem. This library implements the security extensions and cryptographic operations required for secure OAuth flows in AT Protocol applications, including support for DPoP (Demonstration of Proof-of-Possession), PKCE (Proof Key for Code Exchange), and AT Protocol-specific OAuth resource validation.

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

Features#

  • JWT Operations: Complete JSON Web Token minting, verification, and validation with ES256/ES256K support
  • JWK Management: JSON Web Key generation, conversion, and management for P-256 and K-256 curves
  • PKCE Implementation: RFC 7636 Proof Key for Code Exchange for secure OAuth flows
  • DPoP Support: RFC 9449 Demonstration of Proof-of-Possession with automatic retry middleware
  • OAuth Resource Discovery: RFC 8414 OAuth 2.0 authorization server and protected resource metadata discovery
  • AT Protocol Validation: Comprehensive validation of OAuth servers against AT Protocol requirements
  • Structured Error Handling: Type-safe error handling with detailed error codes and messages
  • Cryptographic Integration: Seamless integration with atproto-identity for key operations

Supported OAuth Extensions#

  • PKCE (RFC 7636): Proof Key for Code Exchange with S256 code challenge method
  • DPoP (RFC 9449): Demonstration of Proof-of-Possession with ES256 signing algorithm
  • OAuth 2.0 Resource Discovery (RFC 8414): Authorization server and protected resource metadata
  • JWT Bearer Tokens (RFC 7523): Private key JWT authentication for token endpoints

Installation#

Add this to your Cargo.toml:

[dependencies]
atproto-oauth = "0.6.0"

Usage#

JWT Operations#

use atproto_oauth::jwt::{mint, verify, Header, Claims, JoseClaims};
use atproto_identity::key::identify_key;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    // Parse a private key
    let key_data = identify_key("did:key:zQ3shNzMp4oaaQ1gQRzCxMGXFrSW3NEM1M9T6KCY9eA7HhyEA")?;
    
    // Create JWT header
    let header = Header {
        algorithm: Some("ES256".to_string()),
        type_: Some("JWT".to_string()),
        ..Default::default()
    };
    
    // Create JWT claims
    let claims = Claims::new(JoseClaims {
        issuer: Some("did:plc:issuer123".to_string()),
        subject: Some("did:plc:subject456".to_string()),
        audience: Some("https://pds.example.com".to_string()),
        expiration: Some(chrono::Utc::now().timestamp() as u64 + 3600),
        ..Default::default()
    });
    
    // Mint JWT
    let token = mint(&key_data, &header, &claims)?;
    println!("JWT: {}", token);
    
    // Verify JWT
    verify(&key_data, &token).await?;
    println!("JWT verified successfully!");
    
    Ok(())
}

PKCE Implementation#

use atproto_oauth::pkce;

fn main() {
    // Generate PKCE parameters for authorization flow
    let (code_verifier, code_challenge) = pkce::generate();
    
    println!("Code Challenge: {}", code_challenge);
    println!("Code Verifier: {}", code_verifier);
    
    // Use code_challenge in authorization URL
    let auth_url = format!(
        "https://auth.example.com/oauth/authorize?code_challenge={}&code_challenge_method=S256",
        code_challenge
    );
    
    // Later, use code_verifier when exchanging authorization code for tokens
    // (This would be in your token exchange request)
}

DPoP Proof Generation#

use atproto_oauth::dpop::{auth_dpop, request_dpop};
use atproto_identity::key::identify_key;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let key_data = identify_key("did:key:zQ3shNzMp4oaaQ1gQRzCxMGXFrSW3NEM1M9T6KCY9eA7HhyEA")?;
    
    // Create DPoP proof for authorization request
    let (dpop_token, header, claims) = auth_dpop(
        &key_data,
        "POST",
        "https://auth.example.com/oauth/token"
    )?;
    
    println!("DPoP Authorization Proof: {}", dpop_token);
    
    // Create DPoP proof for resource request
    let (resource_token, _, _) = request_dpop(
        &key_data,
        "GET",
        "https://pds.example.com/api/resource",
        "did:plc:issuer123",
        "access_token_here"
    )?;
    
    println!("DPoP Resource Proof: {}", resource_token);
    
    Ok(())
}

OAuth Resource Discovery and Validation#

use atproto_oauth::resources::{discover_protected_resource, discover_authorization_server, validate};
use reqwest::Client;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let client = Client::new();
    let pds_url = "https://pds.example.com";
    
    // Discover OAuth protected resource configuration
    let protected_resource = discover_protected_resource(&client, pds_url).await?;
    println!("Resource: {}", protected_resource.resource);
    
    // Discover authorization server configuration
    let auth_server_url = &protected_resource.authorization_servers[0];
    let auth_server = discover_authorization_server(&client, auth_server_url).await?;
    
    // Validate AT Protocol requirements
    validate::protected_resource(&protected_resource, pds_url)?;
    validate::authorization_server(&auth_server, pds_url)?;
    
    println!("OAuth configuration validated for AT Protocol compliance!");
    
    Ok(())
}

JWK Generation#

use atproto_oauth::jwk::{generate, WrappedJsonWebKeySet};
use atproto_identity::key::identify_key;

fn main() -> anyhow::Result<()> {
    let key_data = identify_key("did:key:zQ3shNzMp4oaaQ1gQRzCxMGXFrSW3NEM1M9T6KCY9eA7HhyEA")?;
    
    // Generate JWK from key data
    let jwk = generate(&key_data)?;
    println!("JWK Algorithm: {:?}", jwk.alg);
    println!("JWK Key ID: {:?}", jwk.kid);
    
    // Create JWK Set
    let jwk_set = WrappedJsonWebKeySet {
        keys: vec![jwk],
    };
    
    let jwk_json = serde_json::to_string_pretty(&jwk_set)?;
    println!("JWK Set: {}", jwk_json);
    
    Ok(())
}

DPoP Retry Middleware#

use atproto_oauth::dpop::{DpopRetry, auth_dpop};
use reqwest_middleware::ClientBuilder;
use reqwest_chain::ChainableClient;
use atproto_identity::key::identify_key;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let key_data = identify_key("did:key:zQ3shNzMp4oaaQ1gQRzCxMGXFrSW3NEM1M9T6KCY9eA7HhyEA")?;
    
    // Create DPoP proof components
    let (_, header, claims) = auth_dpop(&key_data, "POST", "https://auth.example.com/oauth/token")?;
    
    // Create HTTP client with DPoP retry middleware
    let retry_middleware = DpopRetry::new(header, claims, key_data);
    let client = ClientBuilder::new(reqwest::Client::new())
        .with_chainer(retry_middleware)
        .build();
    
    // The client will automatically retry requests with DPoP nonce if needed
    let response = client
        .post("https://auth.example.com/oauth/token")
        .send()
        .await?;
    
    println!("Response status: {}", response.status());
    
    Ok(())
}

Modules#

  • [jwt] - JSON Web Token minting, verification, and validation
  • [jwk] - JSON Web Key generation and management
  • [pkce] - PKCE (Proof Key for Code Exchange) implementation
  • [dpop] - DPoP (Demonstration of Proof-of-Possession) implementation
  • [resources] - OAuth 2.0 resource discovery and AT Protocol validation
  • [encoding] - Base64 encoding and decoding utilities
  • [errors] - Structured error types for all OAuth operations

AT Protocol Requirements#

This library validates OAuth servers against AT Protocol requirements:

Authorization Server Requirements#

  • Must support authorization_code and refresh_token grant types
  • Must support S256 PKCE code challenge method
  • Must support none and private_key_jwt token endpoint authentication
  • Must support ES256 algorithm for token endpoint authentication
  • Must support atproto and transition:generic scopes
  • Must support ES256 algorithm for DPoP signing
  • Must support authorization response parameters, pushed requests, and client ID metadata

Protected Resource Requirements#

  • Resource URI must match the PDS base URL
  • Must specify exactly one authorization server
  • Authorization server must be properly configured for AT Protocol

Error Handling#

All errors follow a structured format with unique identifiers:

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

Example error categories:

  • error-atproto-oauth-jwt-1 through error-atproto-oauth-jwt-9 - JWT validation errors
  • error-atproto-oauth-client-1 through error-atproto-oauth-client-12 - OAuth client errors
  • error-atproto-oauth-dpop-1 through error-atproto-oauth-dpop-5 - DPoP operation errors
  • error-atproto-oauth-resource-1 through error-atproto-oauth-resource-3 - Resource validation errors
  • error-atproto-oauth-auth-server-1 through error-atproto-oauth-auth-server-12 - Authorization server validation errors
use atproto_oauth::errors::{JWTError, DpopError, OAuthClientError};

// Example error handling
match result {
    Err(JWTError::TokenExpired) => println!("JWT has expired"),
    Err(JWTError::SignatureVerificationFailed) => println!("Invalid JWT signature"),
    Err(DpopError::MissingDpopNonceHeader) => println!("Server didn't provide DPoP nonce"),
    Err(OAuthClientError::InvalidAuthorizationServerResponse(e)) => {
        println!("Authorization server error: {}", e);
    }
    Ok(result) => println!("Success: {:?}", result),
}

Dependencies#

This crate builds on:

  • atproto-identity - Cryptographic key operations and DID resolution
  • reqwest - HTTP client for OAuth server communication
  • serde_json - JSON serialization for OAuth messages and JWT claims
  • chrono - Date and time handling for JWT timestamps
  • base64 - Base64 encoding for JWT and DPoP operations
  • p256 / k256 - Elliptic curve cryptography for ES256/ES256K signatures
  • ulid - Unique identifier generation for JWT IDs and key IDs
  • thiserror - Structured error type derivation

Security Considerations#

  • Always validate OAuth server configurations against AT Protocol requirements
  • Use PKCE for all authorization code flows to prevent authorization code interception
  • Implement DPoP for token requests to bind tokens to specific clients
  • Verify JWT signatures and expiration times before accepting tokens
  • Use cryptographically secure random number generation for PKCE code verifiers
  • Follow RFC specifications for JWT, PKCE, and DPoP implementations

Library Only#

This crate is designed as a library and does not provide command line tools. All functionality is accessed programmatically through the Rust API. For command line operations, see the atproto-identity and atproto-record crates which include CLI tools for identity resolution and record signing operations.

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.