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-identityfor 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.4.1"
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_codeandrefresh_tokengrant types - Must support
S256PKCE code challenge method - Must support
noneandprivate_key_jwttoken endpoint authentication - Must support
ES256algorithm for token endpoint authentication - Must support
atprotoandtransition:genericscopes - Must support
ES256algorithm 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-1througherror-atproto-oauth-jwt-9- JWT validation errorserror-atproto-oauth-client-1througherror-atproto-oauth-client-12- OAuth client errorserror-atproto-oauth-dpop-1througherror-atproto-oauth-dpop-5- DPoP operation errorserror-atproto-oauth-resource-1througherror-atproto-oauth-resource-3- Resource validation errorserror-atproto-oauth-auth-server-1througherror-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 resolutionreqwest- HTTP client for OAuth server communicationserde_json- JSON serialization for OAuth messages and JWT claimschrono- Date and time handling for JWT timestampsbase64- Base64 encoding for JWT and DPoP operationsp256/k256- Elliptic curve cryptography for ES256/ES256K signaturesulid- Unique identifier generation for JWT IDs and key IDsthiserror- 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:
- 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.