A library for ATProtocol identities.
README.md

atproto-xrpcs#

A Rust library providing core building blocks for implementing XRPC services in the AT Protocol ecosystem, with JWT-based authorization extractors for Axum web handlers and DID document verification support.

Overview#

atproto-xrpcs provides foundational components for building AT Protocol XRPC (Cross-Protocol Remote Procedure Call) services. This library offers JWT-based authorization extractors that integrate with Axum web handlers, enabling secure XRPC endpoints with automatic DID document verification and structured error handling.

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

Features#

  • JWT Authorization Extractors: Axum-compatible request extractors for JWT-based authorization
  • DID Document Verification: Automatic verification of caller identities through DID document resolution
  • XRPC Service Components: Core building blocks for implementing AT Protocol XRPC endpoints
  • Structured Error Handling: Comprehensive error types with detailed error codes and messages
  • Axum Integration: Native integration with Axum web framework for HTTP handlers
  • Identity Resolution: Built-in support for resolving AT Protocol identities and handles
  • Security Features: Robust authorization patterns with cryptographic verification

Installation#

Add this to your Cargo.toml:

[dependencies]
atproto-xrpcs = "0.5.0"

Usage#

Basic XRPC Service Setup#

use atproto_xrpcs::authorization::ResolvingAuthorization;
use axum::{Json, Router, extract::Query, routing::get};
use serde::Deserialize;
use serde_json::json;

#[derive(Deserialize)]
struct HelloParams {
    name: Option<String>,
}

// XRPC handler with optional JWT authorization
async fn handle_hello(
    params: Query<HelloParams>,
    authorization: Option<ResolvingAuthorization>,
) -> Json<serde_json::Value> {
    let name = params.name.as_deref().unwrap_or("World");
    
    let message = if authorization.is_some() {
        format!("Hello, authenticated {}!", name)
    } else {
        format!("Hello, {}!", name)
    };
    
    Json(json!({ "message": message }))
}

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    // Set up your Axum router with XRPC endpoints
    let app = Router::new()
        .route("/xrpc/com.example.hello", get(handle_hello))
        .with_state(your_web_context);

    let listener = tokio::net::TcpListener::bind("0.0.0.0:8080").await?;
    axum::serve(listener, app).await?;
    
    Ok(())
}

JWT Authorization with DID Verification#

use atproto_xrpcs::authorization::ResolvingAuthorization;
use axum::{Json, extract::Query};
use serde::Deserialize;
use serde_json::json;

#[derive(Deserialize)]
struct SecureParams {
    data: String,
}

// Handler that requires authentication
async fn handle_secure_endpoint(
    params: Query<SecureParams>,
    authorization: ResolvingAuthorization, // Required authorization
) -> Json<serde_json::Value> {
    // The ResolvingAuthorization extractor automatically:
    // 1. Validates the JWT token
    // 2. Resolves the caller's DID document
    // 3. Verifies the signature against the DID document
    // 4. Provides access to caller identity information
    
    let caller_did = authorization.subject();
    
    Json(json!({
        "message": "Secure operation completed",
        "caller": caller_did,
        "data": params.data
    }))
}

Error Handling in XRPC Services#

use atproto_xrpcs::errors::AuthorizationError;
use axum::{response::IntoResponse, http::StatusCode};

// XRPC services can handle authorization errors appropriately
async fn protected_handler(
    authorization: Result<ResolvingAuthorization, AuthorizationError>,
) -> impl IntoResponse {
    match authorization {
        Ok(auth) => {
            // Handle authenticated request
            (StatusCode::OK, "Access granted").into_response()
        }
        Err(AuthorizationError::InvalidJWTToken { .. }) => {
            (StatusCode::UNAUTHORIZED, "Invalid token").into_response()
        }
        Err(AuthorizationError::DIDDocumentResolutionFailed { .. }) => {
            (StatusCode::FORBIDDEN, "Identity verification failed").into_response()
        }
        Err(_) => {
            (StatusCode::INTERNAL_SERVER_ERROR, "Authorization error").into_response()
        }
    }
}

Integration with AT Protocol Identity System#

use atproto_xrpcs::authorization::ResolvingAuthorization;
use atproto_identity::{
    axum::state::DidDocumentStorageExtractor,
    resolve::IdentityResolver,
};
use axum::extract::State;

// XRPC handlers can access the full AT Protocol identity system
async fn handle_identity_operation(
    authorization: ResolvingAuthorization,
    State(identity_resolver): State<IdentityResolver>,
    State(storage): State<DidDocumentStorageExtractor>,
) -> Json<serde_json::Value> {
    let caller_did = authorization.subject();
    
    // Use the identity resolver for additional operations
    // Access DID document storage for caching and retrieval
    
    Json(json!({
        "caller": caller_did,
        "status": "Identity verified and processed"
    }))
}

Modules#

  • [authorization] - JWT-based authorization extractors with DID document verification
  • [errors] - Structured error types for XRPC authorization operations

Authorization Flow#

The ResolvingAuthorization extractor implements a comprehensive authorization flow:

  1. JWT Extraction: Extracts JWT tokens from HTTP Authorization headers
  2. Token Validation: Validates JWT signature and claims structure
  3. DID Resolution: Resolves the token issuer's DID document
  4. Signature Verification: Verifies the JWT signature against the DID document's public keys
  5. Identity Confirmation: Confirms the caller's identity and authorization scope

Error Handling#

All errors follow a structured format with unique identifiers:

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

Example error categories:

  • error-atproto-xrpcs-authorization-1 through error-atproto-xrpcs-authorization-15 - JWT authorization errors
use atproto_xrpcs::errors::AuthorizationError;

// Example error handling
match authorization_result {
    Err(AuthorizationError::MissingAuthorizationHeader) => {
        println!("No authorization header provided");
    }
    Err(AuthorizationError::InvalidJWTToken { error }) => {
        println!("Invalid JWT token: {}", error);
    }
    Err(AuthorizationError::DIDDocumentResolutionFailed { did, error }) => {
        println!("Failed to resolve DID {}: {}", did, error);
    }
    Ok(auth) => println!("Authorization successful for: {}", auth.subject()),
}

Security Features#

  • JWT Validation: Comprehensive JWT token structure and signature validation
  • DID Verification: Automatic resolution and verification of caller DID documents
  • Cryptographic Security: Integration with atproto-identity for secure key operations
  • Authorization Scope: Support for AT Protocol authorization scopes and permissions
  • Identity Binding: Strong binding between JWT tokens and DID document public keys

Dependencies#

This crate builds on:

  • atproto-identity - Identity resolution and cryptographic operations
  • atproto-oauth - OAuth 2.0 operations and JWT handling
  • atproto-record - AT Protocol record operations
  • axum - Web framework for HTTP handlers and extractors
  • reqwest - HTTP client for DID document resolution
  • serde_json - JSON handling for XRPC request/response data
  • tokio - Async runtime for web service operations
  • thiserror - Structured error type derivation

AT Protocol XRPC#

This library implements components for AT Protocol XRPC, which provides:

  • Cross-Protocol Communication: Standardized RPC over HTTP for AT Protocol services
  • Schema-Driven APIs: Type-safe API definitions using Lexicon schemas
  • Authentication Integration: Seamless integration with AT Protocol identity and authorization
  • Service Discovery: Support for AT Protocol service discovery and routing

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 a complete example implementation, see the atproto-xrpcs-helloworld crate which demonstrates a full XRPC service using these components.

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.