Authentication & Audit
The Manage service authenticates every administrative RPC call and records the privileged work that survives RBAC checks. This document captures the moving pieces so existing contributors remember how the plumbing fits together and new contributors can reuse it correctly.
Access token issuance
AdminAuthService is responsible for exchanging an API key for a signed access
JWT. The AdminLogin processor performs the following steps:
- Look up the submitted API key with
FindAdminPasskeyByToken. Unknown keys immediately returnAdminLoginResult::KeyNotFound. - Resolve the owning administrator via
FindAdminById. Keys referencing a removed account are treated as unknown. - Pull
AdminJwtConfigfrom Redis usingfind_config_from_redis. The config bundle provides the HMAC/EdDSA encoder plus default issuer, audience, and expiry settings. - Build
AdminJwtClaimsfrom the admin profile (ID, display name, role) and the timestamps computed fromOffsetDateTime::now_utc(). - Sign the JWT with the encoder returned by the config and wrap the token in
AdminAccessTokenfor the RPC response.
Every Manage client must attach the returned JWT to subsequent requests using
x-admin-authorization. No refresh token logic exists—clients repeat the login
flow when the token expires.
Authentication middleware
The RPC server mounts AdminAuthLayer (see rpc::middleware) on every handler
that requires an authenticated admin. The middleware:
- Loads
AdminJwtConfigfrom Redis (identical to the login flow) so it can reuse the configured decoder. - Extracts
x-admin-authorizationfrom the incoming headers and validates the JWT. Invalid or missing tokens result inStatus::unauthenticatedonce the gRPC method executes. - Stores the authenticated administrator ID in the request extensions as
AdminIdso downstream handlers can fetch it withAdminId::from_request(&Request).
When you add a new RPC surface, ensure the router is wrapped with
AdminAuthLayer::new(redis.clone()) and read the admin identifier from the
extensions instead of parsing the header manually. This keeps every handler in
sync with the central decoding logic and JWT configuration source.
Auditing and RBAC enforcement
AuditLayer wires authentication into authorization and durable audit trails.
Operations that mutate state are modelled as RecordedAdminOperation<T> where
T: AdminOperation describes the action being performed. Wrapping a processor
with the layer performs the following steps:
- Reload the administrator from the database (
FindAdminById). Requests referencing a deleted admin fail withError::PermissionsDenied. - Emit tracing fields (
admin_name,admin_role) for observability. - Check whether the admin role satisfies the static allow-list declared on the
operation type via
AdminOperation::check_permission. Permission failures short-circuit the call withError::PermissionsDenied. - If permitted, transform the operation into an audit payload with
to_audit_log()and persist it usingAddAuditLog. - Execute the wrapped processor and log success or failure.
Use AuditLayer::wrap_without_record when you only need the permission check
(for example, read-only operations). Otherwise prefer wrap so the audit table
stays authoritative.
Checklist for new handlers
- Accept the admin context by calling
AdminId::from_requestin the RPC entry point. - Build the corresponding operation type that implements
AdminOperation. - Wrap the service processor with
AuditLayer::wrap(orwrap_without_recordfor read endpoints). - Propagate
Error::PermissionsDeniedback to the client untouched so callers see a clear 403-style error.
Following these conventions ensures every manage RPC reuses the same JWT validation, role checks, and audit recording logic.