Authentication and signing
Gateman authenticates applications, not individual end users. An authenticated request identifies the application that is calling Gateman, proves possession of that application's configured signing credential, and then checks whether that application is authorized to use the requested capability.
This page explains the application identity, authentication, authorization, admission, and credential-disclosure protections that operate through Contract v1. Contract v1 defines how to construct and sign the authenticated HTTP request. This page explains what Gateman does with that request after it arrives.
Authentication model
Every authenticated application request identifies its application with X-GM-App. Gateman resolves that identifier against the application's server-side record. The record determines the authentication method, credential material, application status, scopes, and any source-IP restrictions.
Authentication and authorization are separate:
| Concern | Question answered | Gateman control |
|---|---|---|
| Application identity | Which application is making this request? | X-GM-App and the application registry |
| Authentication | Does the caller possess the configured application credential? | HMAC-SHA256 or Ed25519 signature verification |
| Authorization | May this application use this Gateman capability? | Exact required capability scope |
| Request admission | May this authenticated request enter processing now? | Source-IP restrictions and per-application concurrency admission |
An application must have active status to authenticate. A disabled or unknown application is rejected before capability processing. Application signing credentials are separate from downstream provider credentials such as Stripe, AWS, or email-provider credentials.
X-GM-App values must be non-empty after trimming and no longer than 128 bytes. Gateman does not use an end-user login, browser session, role hierarchy, wildcard scope, or automatic scope elevation for application API authentication.
Supported authentication methods
The application's configured authentication method is selected from its Gateman application record. Gateman does not select a method dynamically from whichever credential-looking headers are present, and it does not automatically fall back between methods.
HMAC-SHA256
HMAC authentication uses a shared application secret. Gateman stores the application secret encrypted. During authentication, it retrieves and decrypts that secret server-side, calculates HMAC-SHA256 over the Contract v1 signing input, and compares the result in constant time.
The canonical signature encoding is lowercase hexadecimal. The current server also accepts uppercase hexadecimal after normalizing the received signature, but applications should send the canonical lowercase form.
The application must keep its HMAC secret confidential. Anyone who obtains it can authenticate as that application.
Ed25519
Ed25519 authentication uses an asymmetric key pair. The application keeps its private signing key and creates a detached Ed25519 signature over the Contract v1 signing input. Gateman stores the corresponding raw 32-byte public key and verifies the detached signature.
The signature must be exactly 64 bytes before encoding and must be sent as canonical unpadded Base64url. Padding, alternate Base64 encodings, invalid characters, and signatures with the wrong decoded length are rejected.
The private key must remain application-side. It must not be placed in browser-side code or exposed to another untrusted client.
Constructing the signed request
The exact headers, signing input, raw-body rules, timestamp rules, HMAC encoding, and Ed25519 encoding are defined by Contract v1. In particular, the exact raw body bytes sent over the connection must be the bytes that were signed; Gateman does not parse and reserialize the body before signature verification.
The shared request envelope includes X-GM-App, X-GM-Ts, and X-GM-Sig. X-GM-Idempotency-Key is optional and its normalized value is signed. X-GM-Credential-Request-ID is additionally required and signed for POST /secrets/get and POST /db/get.
Authenticated request lifecycle
For an authenticated application route, Gateman processes the request in this order:
- Resolve the incoming method and path to a route.
- Validate the authentication headers required by the shared request envelope.
- Validate
X-GM-Credential-Request-IDwhen the route requires it. - Resolve the application from Gateman's application registry.
- Require an active application and valid configured credential material.
- Enforce any configured exact source-IP restriction.
- Validate the timestamp syntax and freshness window.
- Read the raw body and verify the HMAC-SHA256 or Ed25519 signature.
- Check the route's required capability scope.
- Admit the authenticated request under the per-application concurrency limit.
- Run route-specific validation, operation accounting, idempotency, provider concurrency, and capability processing.
An authentication or authorization rejection stops processing at that point. It does not start provider execution or route-specific operation accounting. Shared provider-bound concurrency controls and monthly operation accounting are later capability controls; they are not substitutes for application authentication or shared request admission.
Public routes such as GET /health and the public canonical-client acquisition routes do not use this application authentication path. Provider webhooks and other externally authenticated routes use their own authentication contracts.
Authentication and authorization
Authentication establishes that the caller possesses the signing credential configured for the application identified by X-GM-App. It does not grant access to every Gateman capability.
Authorization is based on exact scope identifiers assigned to the application record. Each authenticated route declares the scope it requires. The request is rejected when the application does not contain that exact scope.
Examples include:
email.sendfor email sending routes;sms.sendfor supported SMS routes;stripe:useforPOST /stripe/request;secrets:readfor ordinary stored-credential retrieval;db:readfor named database-password retrieval;client:readforPOST /client/getand public-configuration retrieval;anti_bot.verifyfor the Turnstile and reCAPTCHA credential routes.
Determine the scopes an application needs from the individual capability documentation for the routes it will call. Gateman scopes authorize use of Gateman capabilities; they are not provider IAM policies, provider account permissions, subscription entitlements, or proof of ownership of a provider resource.
Timestamp freshness and replay boundaries
X-GM-Ts must contain a decimal Unix timestamp in seconds. Gateman accepts a request when its timestamp is no more than five minutes in the past or future relative to the server clock. The exact supplied timestamp text is included in the signed input.
This timestamp check limits how long a signed request remains acceptable. It is not universal one-time replay prevention. An otherwise valid ordinary signed request may be replayed within the accepted window unless the selected capability applies its own idempotency or single-use control.
These protections have different purposes:
| Protection | Purpose |
|---|---|
| Signature authenticity | Proves possession of the configured application credential and protects the signed request components from modification. |
| Timestamp freshness | Rejects requests outside the approximately five-minute acceptance window. |
| Capability idempotency | Prevents or controls repeated execution for capabilities that define an idempotency contract. |
| Credential request claims | Makes a credential-disclosure request identifier single-use for one application. |
See Contract v1 for the precise timestamp and signing-input rules. HTTPS and request signing provide different protections: HTTPS protects the transport connection, while signing authenticates the application request and its signed components.
Application access restrictions
Application status
Only applications with active status may authenticate. Disabled applications are rejected as unauthenticated applications; they do not proceed to scope checks or capability execution.
Source-IP restrictions
An application may be configured with an allowlist of exact IPv4 or IPv6 addresses. The current application validation does not support CIDR ranges.
Gateman uses the resolved request source address. It does not trust an arbitrary client-supplied X-Forwarded-For or similar forwarding header in application code. In the deployed reverse-proxy configuration, Nginx resolves Cloudflare's CF-Connecting-IP only when the immediate peer is in the configured Cloudflare address ranges; the application then evaluates the resulting request source address.
If a configured source-IP restriction does not match, the request is rejected with HTTP 401 before timestamp or signature verification completes.
Capability scopes
Scopes are checked after the signature is verified and before shared request admission. A missing required scope returns HTTP 401 with Scope not allowed.
Request admission
After authentication and scope authorization, Gateman applies shared non-blocking per-application request admission. The current implementation permits up to six simultaneous authenticated application requests per application. This is an internal admission control, not an immutable public service-level guarantee.
When all application admission slots are occupied, Gateman returns HTTP 429 and Retry-After: 1 with the error app_concurrency_limit_exceeded. If the admission mechanism is unavailable, it returns HTTP 503 with app_concurrency_guard_unavailable.
This shared admission limit is separate from shared provider-bound concurrency controls, which apply only on applicable capability paths when a capability is about to use a provider.
Credential-disclosure protection
POST /secrets/get and POST /db/get have an additional single-use protection because these routes can disclose credential values.
They require:
X-GM-Credential-Request-ID: 32 lowercase hexadecimal characters
The identifier is included as its own signed component between the normalized idempotency-key component and the raw body. See Contract v1 for the exact signing-input layout.
After the request has authenticated and its route-specific request shape has been validated, Gateman claims the identifier for the authenticated application. The claim is stored using the application and request identifier as a unique pair.
- The first claim for an application succeeds.
- Reusing the same identifier for that application returns HTTP 409 with
Credential request already used. - The same identifier can be claimed independently by another application.
- Authentication failures do not create a claim.
- Claim-storage failure fails closed with HTTP 503.
- Claims are retained for 11 minutes, allowing the claim to cover the full timestamp acceptance period plus a margin.
This is distinct from ordinary idempotency. It is a single-use disclosure claim, not a request-result replay store, and it does not make ordinary authenticated requests single-use.
Authentication failures
The shared application authentication layer returns JSON with ok: false and an error value. Responses use Cache-Control: no-store. A request correlation value is returned in X-Gateman-Request-ID when request processing has started.
The major shared failure classes are:
| Condition | HTTP status | Meaning |
|---|---|---|
| Missing required authentication headers | 401 | One or more required shared authentication headers are absent. |
| Invalid credential request ID | 401 | A credential-disclosure route lacks a valid request identifier. |
| Unknown or inactive application | 401 | X-GM-App does not resolve to an active application. |
| Deterministically missing or invalid application credential | 401 | The application record was read successfully, but its configured authentication material is missing or invalid. |
| Invalid timestamp syntax | 401 | X-GM-Ts is not decimal Unix-seconds text. |
| Stale request | 401 | The timestamp is outside the accepted freshness window. |
| Invalid signature | 401 | The calculated HMAC or Ed25519 signature does not verify. |
| Source-IP restriction failure | 401 | The resolved source address is not in the application's exact allowlist. |
| Missing required scope | 401 | The authenticated application lacks the route's exact required scope. |
| Application admission limit | 429 | The application's shared concurrent-request capacity is currently full. Retry-After: 1 is included. |
| Application authentication infrastructure unavailable | 503 | Gateman cannot read the application registry or construct/use the authentication credential store. The public error is Authentication unavailable. |
| Application admission infrastructure unavailable | 503 | The shared concurrency-admission mechanism is unavailable. The public error is app_concurrency_guard_unavailable. |
These statuses describe the shared authentication and admission boundary. Capability validation, operation accounting, provider concurrency, provider-native errors, and capability-specific idempotency may return other statuses and error shapes. HTTP status is authoritative; a non-2xx response is not necessarily a transport failure.
Authentication responses do not disclose application secrets, provider credentials, signatures, raw bodies, private keys, or signing diagnostics.
Credential handling and security responsibilities
Application signing material and downstream provider credentials have separate security boundaries.
- Keep HMAC application secrets confidential.
- Keep Ed25519 private keys in the application-side secret store or other trusted server-side runtime.
- Do not place signing material in browser-side code or untrusted client applications.
- Gateman stores Ed25519 public keys for verification; the private key is not required by Gateman.
- Do not confuse an application signing credential with a provider credential stored or used by Gateman.
- Sign the exact request components required by Contract v1 so changes to those components are detected.
- Do not assume that an ordinary signed request can only be used once; use the capability's idempotency contract where repeated execution matters.
- Use the dedicated credential-request identifier behavior for credential-disclosure routes.
HTTPS and deployment security
Gateman uses Cloudflare to enforce HTTPS and HSTS, with TLS connections to the origin secured using Cloudflare origin certificates. The origin also enforces HTTPS and uses trusted Cloudflare source-IP handling.
Contract v1 request signing does not establish TLS. HTTPS protects the connection in transit; request signing authenticates the application request and protects its signed method, path, selected headers, and raw body from modification.
