Auth Service

mero-auth

Purpose

Forward authentication service for the Calimero node. Handles JWT issuing and verification, pluggable authentication providers (NEAR, Ethereum, custom), a challenge/nonce flow for wallet-based auth, and key management. Can run embedded in the Axum server or behind an external reverse proxy.

JWT
token system
2
auth modes
N
pluggable providers
Ed25519
key management

Architecture

The AuthService coordinates four subsystems: token management, provider routing, key storage, and secret management. Incoming token requests are routed by auth_method to the appropriate provider.

TokenRequest auth_method + credentials AuthService TokenManager encode / decode with jsonwebtoken JwtConfig · Access + Refresh tokens AuthProviders pluggable · route by auth_method NEAR Ethereum Custom KeyManager signing keys Ed25519 · rotation SecretManager JWT secrets HMAC · rotation RocksDB / In-Memory Store TokenResponse access_token + refresh_token Challenge Flow 1. Request challenge nonce 2. Sign with wallet key 3. Submit signed challenge Token requests routed by auth_method to pluggable providers — challenge/nonce for wallet-based authentication
AuthService boundary
Token management
Provider routing
Key management
Secret management

JWT Details

Token structure, claims schema, and the challenge-based authentication flow.

TokenManager

Encodes and decodes JWTs using the jsonwebtoken crate. Configured via JwtConfig which specifies algorithm (HS256/HS384), secret rotation policy, and expiration windows for access and refresh tokens.

encode()
Claims → signed JWT string
decode()
JWT string → validated Claims
refresh()
Refresh token → new access token

TokenType

Two token variants with different lifetimes and permissions scope.

pub enum TokenType {
    Access,  // short-lived, carries permissions
    Refresh, // long-lived, exchange for new access
}

Claims Schema

pub struct Claims {
    sub: String,          // key id (public key hash)
    iss: String,          // issuer (node identity)
    aud: String,          // audience
    exp: u64,             // expiration timestamp
    iat: u64,             // issued at
    jti: String,          // unique token id (UUID)
    permissions: Vec<String>, // granted permissions
    node_url: Option<String>, // optional node URL
}

ChallengeClaims

pub struct ChallengeClaims {
    challenge: String, // random nonce string
    exp: u64,          // challenge expiration (short TTL)
    iat: u64,          // issued at
}
Challenge / Nonce Authentication Flow
1

Request Challenge

Client calls the challenge endpoint. The AuthService generates a random nonce, wraps it in ChallengeClaims, and returns a short-lived challenge JWT (e.g. 5-minute TTL).

2

Sign Challenge

Client signs the challenge nonce with their wallet key (NEAR Ed25519, Ethereum secp256k1, etc.). The signature proves ownership of the corresponding public key without revealing the private key.

3

Submit Signed Challenge

Client sends the signed challenge back along with their public key and auth_method. The appropriate AuthProvider verifies the signature against the public key and challenge nonce.

4

Issue Tokens

On successful verification, TokenManager generates an access token and refresh token with the authenticated key ID as sub claim. Tokens are returned to the client for subsequent API calls.

Auth Modes

Two operational modes that determine how authentication is enforced on incoming requests.

Proxy Mode

external

No in-process JWT verification. Authentication is handled by an external reverse proxy (nginx, Envoy, etc.) that sits in front of the Calimero node. The proxy validates tokens and forwards authenticated requests with identity headers.

The node trusts the proxy's identity injection and does not perform any token validation itself. Suitable for enterprise deployments with existing auth infrastructure.

// Proxy mode config
struct ProxyConfig {
    trusted_header: String, // e.g. "X-Auth-Key"
}

Embedded Mode

recommended

Full JWT lifecycle managed in-process. The AuthGuardLayer middleware intercepts every request, extracts the token from the Authorization: Bearer header or ?token= query parameter, and verifies it against the local secret.

On successful verification, an AuthenticatedKey is injected into the Axum request extensions, making the caller's identity available to all downstream handlers.

// Embedded mode middleware
struct AuthGuardLayer {
    token_manager: Arc<TokenManager>,
}

// Injected into request extensions
struct AuthenticatedKey {
    key_id: String,
    permissions: Vec<String>,
}

Storage

Auth state can be persisted in RocksDB (production) or held in-memory (development/testing). The storage backend is selected at initialization and abstracted behind a trait interface.

RocksDB Backend

Persistent storage for signing keys, refresh token metadata, and revocation lists. Uses a dedicated column family within the node's RocksDB instance. Survives node restarts.

In-Memory Backend

Ephemeral storage for development and testing. All auth state is lost on restart. Useful for local development where persistent tokens are unnecessary.

crates/mero-auth

API Reference

Public Endpoints

GET /auth/login — React SPA for authentication GET /auth/challenge — Get signing challenge POST /auth/token — Exchange challenge for root key JWT POST /auth/refresh — Refresh expired tokens GET /auth/providers — List available providers GET /auth/identity — Service information GET /auth/validate — Forward auth validation endpoint

Protected Endpoints

— Root Key Management — GET /admin/keys — List root keys POST /admin/keys — Create root key DELETE /admin/keys/{key_id} — Delete root key — Client Key Management — GET /admin/keys/clients — List client keys POST /admin/client-key — Generate client key DELETE /admin/keys/{key_id}/clients/{client_id} — Delete client key — Permissions — GET /admin/keys/{key_id}/permissions — Get key permissions PUT /admin/keys/{key_id}/permissions — Update permissions — Token & System — POST /admin/revoke — Revoke current token GET /admin/metrics — Service metrics

Deployment

Nginx (Forward Auth)

Configure auth_request to proxy to /auth/validate. Protected routes get validated transparently. Auth routes (/auth/*, /admin/*) proxy directly to the auth service.

Traefik

Use forwardauth.address middleware pointing to http://auth:3001/auth/validate. Label auth service routes with PathPrefix(/auth) and node routes with the forward-auth middleware.

Configuration

All settings can be overridden via AUTH_-prefixed environment variables (e.g. AUTH_JWT__ISSUER, AUTH_STORAGE__TYPE).

config.toml example
listen_addr = "0.0.0.0:3001" [jwt] issuer = "calimero-auth" access_token_expiry = 3600 refresh_token_expiry = 2592000 [storage] type = "rocksdb" path = "/data/auth_db" [providers] near_wallet = true