arete-auth
Authentication and authorization utilities for Arete, providing JWT token handling, claims validation, and Ed25519-based key management for secure WebSocket and HTTP authentication.
Overview
arete-auth provides a robust, production-ready authentication system designed specifically for Arete deployments. It uses Ed25519 (EdDSA) for asymmetric cryptographic signing, offering superior security compared to traditional HMAC-based approaches.
Key Features
- Ed25519 Signatures: Asymmetric signing using the EdDSA algorithm for enhanced security
- JWT Token Support: Full JWT implementation with customizable session claims
- Key Rotation: JWKS (JSON Web Key Set) support for seamless key rotation
- Origin Binding: Optional defense-in-depth with origin validation
- IP Binding: Client IP validation for high-security scenarios
- Resource Limits: Built-in metering and rate limiting support
- Token Revocation: Support for token revocation lists
- Security Auditing: Structured audit logging for authentication events
- Multi-key Verification: Verify tokens against multiple signing keys
Installation
Add to your Cargo.toml:
[]
= "0.2"
Feature Flags
jwks(default): Enables JWKS fetching from URLs viareqwest
[]
= { = "0.2", = false } # Without JWKS
Usage
Basic Token Signing and Verification
use ;
// Generate a new Ed25519 key pair
let signing_key = generate;
let verifying_key = signing_key.verifying_key;
// Create a signer with your issuer
let signer = new;
// Build session claims
let claims = builder
.with_ttl // 5 minute session
.with_scope
.with_metering_key
.build;
// Sign the token
let token = signer.sign.unwrap;
// Verify the token
let verifier = new;
let auth_context = verifier.verify.unwrap;
println!;
Typed Solana Gateway Claims
Regional gateways use the stable audience arete:solana-gateway and target
kind solana-gateway-binding. Build and validate those claims with the typed
helpers rather than duplicating wire strings:
use ;
let claims = solana_gateway_builder
.with_scope
.build;
let authorization = try_from_context?;
The only gateway scopes are the exact whitespace-delimited values read,
transaction:inspect, transaction:send, and fork:read; no scope implies
another. fork:read authorizes only the read-only fork JSON-RPC endpoint
(POST /rpc/v1/fork).
Legacy deployment claims and their audience remain unchanged.
Key Management
use ;
use Path;
// Generate and save keys to files
let = generate_and_save_keys.unwrap;
// Load keys from files
let signing_key = signing_key_from_file.unwrap;
let verifying_key = verifying_key_from_file.unwrap;
// Load from environment variables (base64-encoded)
set_var;
let signing_key = signing_key_from_env.unwrap;
Origin and IP Binding
Add defense-in-depth by binding tokens to specific origins or client IPs:
use ;
let signing_key = generate;
// Create origin-bound token
let claims = builder
.with_origin
.with_client_ip
.build;
let signer = new;
let token = signer.sign.unwrap;
// Verify with origin validation
let verifier = new
.with_origin_validation
.with_client_ip_validation;
// Must provide matching origin and IP
let context = verifier.verify.unwrap;
Resource Limits and Metering
use ;
let limits = Limits ;
let claims = builder
.with_limits
.with_key_class // or KeyClass::Secret
.with_plan
.build;
Account Policy Identities (v2)
Signed sessions can carry explicit actor/account/consumer identities plus a monotonic policy version and aggregate account limits. All five fields are optional on the wire, so old tokens keep verifying unchanged, but partial subsets are rejected at verification time:
- an authenticated v2 token carries
actor_key,account_key,consumer_key,policy_version, andaccount_limitstogether; - an anonymous v2 token carries
actor_key,consumer_key, andpolicy_versionwithplan = "anonymous"and no account fields; - a legacy token carries none of the new fields.
limits stays consumer/connection-scoped; account_limits is the aggregate
budget shared by every consumer of one billing account. Identities are opaque
keys of 1-512 bytes drawn from [A-Za-z0-9._:@/-].
use ;
let claims = builder
.with_metering_key
.with_plan
.with_actor_key
.with_account_key
.with_consumer_key
.with_policy_version
.with_limits
.with_account_limits
.build;
Readers resolve identities once through AuthContext (and the propagated
ProgramReadAuthorization / SolanaGatewayAuthorization):
let actor = context.actor_key; // actor_key, falling back to sub
let consumer = context.consumer_key; // consumer_key, falling back to sub
let account = context.account_key; // account_key, falling back to metering_key
let legacy = context.is_legacy_policy;
Never repurpose metering_key as a consumer limiter for new tokens; it is an
account attribution key. Keep fallback logic in these helpers rather than
duplicating it in runtimes.
Sealed Claims for Browser Sessions
A session token is signed, not encrypted, so whoever holds it can read every
claim. A token handed to a browser should not tell its visitor about the
account behind it. TokenSigner::sign_sealed moves the account claims
(ACCOUNT_CLAIMS: metering_key, limits, plan, actor_key,
account_key, consumer_key, policy_version, account_limits) into one
sealed claim, encrypted with XChaCha20-Poly1305 under a key the issuer and
the verifying services share. The ciphertext is bound to the token's iss,
aud and jti.
Verifiers given the keys open the sealed claim after the signature check, so
AuthContext and every limit built from it are the same as for an unsealed
token. A verifier without the keys refuses a sealed token.
use ;
let seal = from_base64?;
let token = new.sign_sealed?;
// `id=base64[,id=base64...]`; the first key seals, all of them open.
let keys = parse?;
let context = new
.with_claim_seal_keys
.verify?;
Issuers that build claims as JSON can call seal_claims and unseal_claims
directly on the claims object. To rotate, add the new key behind the current
one on every verifier, then put it first on the issuer, then drop the old key
once the tokens it sealed have expired.
JWKS Key Rotation
use JwksVerifier;
// Fetch JWKS from a URL
let jwks = fetch_jwks
.await
.unwrap;
// Create verifier with JWKS support
let verifier = new;
// Tokens signed with any key in the JWKS can be verified
let context = verifier.verify.unwrap;
Multi-key Verification
Verify tokens against multiple keys (useful for zero-downtime rotation):
use ;
let verifier = builder
.with_key
.with_key
.build;
let context = verifier.verify.unwrap;
Security Audit Logging
use ;
// Log authentication events
let logger = new;
logger.log_event.await;
Security Considerations
- Key Storage: Store signing keys securely (e.g., in environment variables, AWS KMS, or HashiCorp Vault)
- Token TTL: Use short-lived tokens (default: 5 minutes) with refresh mechanisms
- Origin Binding: Enable origin validation for browser-based clients
- Rate Limiting: Implement rate limiting using the built-in metering support
License
This project is licensed under the terms specified in the LICENSE file.
See Also
- KEY_ROTATION_GUIDE.md - Guide for key rotation procedures
- SECURITY_AUDIT_LOGGING.md - Security audit logging documentation