authentication-verifier 0.1.0

Offline RS256 JWT verification for the Main X Index federation: fetch the authentication-service JWKS once, then verify peer-service bearer tokens locally — no shared secret, no per-request introspection hop.
Documentation

authentication-verifier

Offline RS256 JWT verification for Main X Index peer services.

The authentication-service is the federation's single sign-on provider: it signs RS256 access tokens and publishes its RSA public keys at /.well-known/jwks.json. Every other service verifies those tokens offline with this crate — fetch the JWKS once at boot, build a Verifier, then verify each bearer token locally. No shared secret, no per-request introspection hop.

Quick start

[dependencies]
authentication-verifier = { path = "../authentication/authentication-verifier-rust-crate" }
# or, to fetch the JWKS over HTTPS at boot:
# authentication-verifier = { path = "...", features = ["fetch"] }
use authentication_verifier::{Verifier, Claims, VerifyError};

// Boot: build from a JWKS document you already have…
let jwks: serde_json::Value = serde_json::from_str(jwks_body)?;
let verifier = Verifier::from_jwks_value(&jwks, "authentication-service", "main-x-service")?;

// …or fetch it over HTTPS (requires the `fetch` feature):
// let verifier = Verifier::from_jwks_url(
//     "https://auth.example.gov/.well-known/jwks.json",
//     "authentication-service",
//     "main-x-service",
// ).await?;

// Per request: verify the bearer token locally.
let claims: Claims = verifier.verify(bearer_token)?;
println!("authenticated subject: {}", claims.sub); // the user pid (UUID)

Construct the Verifier once and share it behind an Arc; verify is read-only and allocation-light.

API summary

Item Purpose
Verifier::from_jwks_value(&jwks, issuer, audience) Build from an in-memory JWKS document. Loads RSA keys only (non-RSA kty entries are skipped); an empty key set is permitted and rejects every token with UnknownKid.
Verifier::from_jwks_url(url, issuer, audience).await Fetch the JWKS over HTTPS, then build. Requires features = ["fetch"] (pulls in reqwest with rustls).
Verifier::verify(token) -> Result<Claims, VerifyError> Select the key by the token header kid, check the RS256 signature, then enforce iss, aud, and exp.
Verifier::key_count() -> usize Number of RSA keys loaded from the JWKS.
Claims Verified claims: sub (user pid, UUID string), email, name, iss, aud, exp, iat, jti (= the auth-service sessions.jid).

VerifyError variants

Variant Meaning
Jwks(String) The JWKS document was missing or structurally invalid (no keys array; RSA entry missing kid/n/e; bad modulus/exponent).
MissingKid The token header carries no kid, so no key can be selected.
UnknownKid(String) No loaded key matches the token's kid — stale JWKS cache, wrong issuer, or forgery. Refetch the JWKS to pick up a key rotation.
Jwt(jsonwebtoken::errors::Error) Signature, issuer, audience, or expiry validation failed.
Fetch(String) Fetching the JWKS over HTTP failed (only with the fetch feature).

The JWKS / kid contract

This crate is the peer-side mirror of the auth-service's own token verification: same RS256 algorithm, same Claims shape, same kid selection — but keyed off the published JWKS rather than a locally held key, so any service can embed it.

  • The service publishes RSA signing keys as {kty: "RSA", use: "sig", alg: "RS256", kid, n, e} with n/e base64url (no padding).
  • kid = base64url, no padding, of SHA-256(big-endian RSA modulus bytes) — a stable thumbprint, stamped into every token header.
  • Claims is duplicated byte-identically between the service and this crate by convention; the service's cross-crate contract test pins the round-trip.
  • Default issuer/audience: authentication-service / main-x-service (the service's JWT_ISSUER / JWT_AUDIENCE).
  • Revocation (signout) is enforced only at the auth service. Peers honour a token until exp; the service keeps TTLs short (default 3600 s) to bound that window.

Testing

cargo test                  # offline unit tests (throwaway RSA keypair)
cargo test --features fetch # also compile the HTTP loader

License

MIT OR Apache-2.0 OR GPL-2.0 OR GPL-3.0 OR BSD-3-Clause