A library for ATProtocol identities.
README.md

atproto-xrpcs#

XRPC service components library for AT Protocol with JWT authorization.

Overview#

atproto-xrpcs provides foundational components for building AT Protocol XRPC services. This library offers JWT-based authorization extractors that integrate with Axum web handlers, enabling secure XRPC endpoints with automatic DID document verification.

Binaries#

  • atproto-xrpcs-helloworld: Complete example XRPC service with DID:web functionality (via atproto-xrpcs-helloworld crate)

Features#

  • JWT authorization extractors for Axum web handlers
  • Automatic DID document verification for caller identities
  • Structured error handling with detailed error codes
  • Native Axum integration for HTTP handlers
  • Identity resolution support for AT Protocol

Usage#

Basic XRPC Service#

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>,
}

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 }))
}

let app = Router::new()
    .route("/xrpc/com.example.hello", get(handle_hello))
    .with_state(your_web_context);

JWT Authorization#

use atproto_xrpcs::authorization::ResolvingAuthorization;

async fn handle_secure_endpoint(
    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!({"caller": caller_did, "status": "authenticated"}))
}

Error Handling#

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

async fn protected_handler(
    authorization: Result<ResolvingAuthorization, AuthorizationError>,
) -> impl IntoResponse {
    match authorization {
        Ok(auth) => (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()
        }
    }
}

Authorization Flow#

The ResolvingAuthorization extractor implements:

  1. JWT extraction from HTTP Authorization headers
  2. Token validation (signature and claims structure)
  3. DID resolution for the token issuer
  4. Signature verification against DID document public keys
  5. Identity confirmation and authorization scope validation

License#

MIT License