Loading…
Loading…
Explains how the SaaS tenant resolver identifies and authenticates tenants for inbound API requests using a tenant slug and a dedicated API key.
The SaasTenantResolver is a gateway component responsible for identifying and authenticating the tenant for each inbound API request in a multi-tenant environment. It serves as the primary mechanism for enforcing tenant isolation at the edge of the platform.
For every request, the resolver performs two critical functions:
Host header or the request path for a unique tenant identifier, known as a "slug".X-S2R-Tenant-Key header.The resolver takes the request context as input, which includes HTTP headers and the path. On successful authentication, it outputs the unique UUID of the tenant, allowing downstream components to operate within the correct tenant context. If any validation step fails, it rejects the request with a TenantAuthenticationException, preventing unauthorized or ambiguous access.
The SaasTenantResolver executes a strict, sequential process to resolve and authenticate a tenant. The process is initiated by an upstream request filter that calls the resolve(TenantResolutionContext) method.
flowchart TD
subgraph "API Gateway"
A[Inbound API Request] --> R[Tenant Resolver]
end
subgraph "Resolution and Authentication Flow"
R --> B[1. Extract Tenant Slug<br/>from Host or Path]
R --> C[2. Extract API Key<br/>from X-S2R-Tenant-Key Header]
B & C --> D{Slug and Key Provided?}
D -- No --> E1[Fail: UNRESOLVED_SLUG or MISSING_KEY]
D -- Yes --> F[3. Hash API Key<br/>SHA-256 Hex]
F --> G[4. Look Up Hashed Key]
G -- Not Found --> E2[Fail: INVALID_KEY]
G -- Found --> H{5. Is Key Active?}
H -- No --> E3[Fail: REVOKED_KEY]
H -- Yes --> I[6. Check Tenant Status]
I -- Not Active --> E4[Fail: SUSPENDED_TENANT]
I -- Active --> J[7. Resolve Tenant Slug]
J -- Not Found --> E1
J -- Found --> K{8. Match Key's Tenant to Slug's Tenant}
K -- Mismatch --> E5[Fail: TENANT_MISMATCH]
K -- Match --> L[9. Update Key Usage Timestamp]
L --> M[Success: Return Authenticated Tenant ID]
endThe sequence of operations is as follows:
Host header. If not found, it falls back to checking for a /t/{slug}/ prefix in the request path. If no slug can be found, the request is rejected.X-S2R-Tenant-Key HTTP header. If the header is missing or empty, the request is rejected.TenantApiKeyRepository using the key's SHA-256 hash. This lookup bypasses standard row-level security to find the key across all tenants. If no key matches the hash, the request is rejected.active. If the key has been revoked or is otherwise inactive, the request is rejected.active), the request is rejected. This ensures that disabling a tenant immediately cuts off all access, even with valid API keys.TenantRegistryRepository to find the active tenant ID corresponding to the slug extracted in step 1. If the slug does not map to an active tenant, the request is rejected.last_used_at timestamp for the API key. This operation runs in the context of the authenticated tenant (using TenantContext.runAs) and does not fail the request if the update fails. The resolver then returns the authenticated tenant's UUID.IMPORTANT
The resolver never logs the presented API key or its computed hash, in adherence with the platform's no-secret-logging policy.
The resolver identifies the target tenant using one of two methods, in order of preference.
The primary method for identifying a tenant is via the subdomain in the Host header. The resolver extracts the first label of the hostname.
gatewayBaseDomain (e.g., api.example.com), a request with Host: my-tenant.api.example.com will resolve the slug my-tenant.gatewayBaseDomain is configured.Host header before processing.api.example.com) or a multi-level subdomain (e.g., test.my-tenant.api.example.com) will not yield a slug.If the Host header does not yield a tenant slug, the resolver falls back to inspecting the request URI path.
/t/{slug}/. For a request to /t/my-tenant/v1/resource, the resolver extracts the slug my-tenant./t/.Authentication is performed by validating a per-tenant API key.
API consumers must provide their key in a dedicated HTTP header.
| Header | Description | Required |
|---|---|---|
X-S2R-Tenant-Key | The per-tenant API key for authentication. | Yes |
To avoid storing or comparing raw credentials, the resolver hashes the provided API key using SHA-256 and compares the hex digest against stored values. The hashing implementation is a public static method, sha256Hex, to ensure consistent hashing logic across different components that may need to perform the same operation.
public static String sha256Hex(String value) {
try {
MessageDigest md = MessageDigest.getInstance("SHA-256");
byte[] digest = md.digest(value.getBytes(StandardCharsets.UTF_8));
StringBuilder sb = new StringBuilder(digest.length * 2);
for (byte b : digest) {
sb.append(Character.forDigit((b >> 4) & 0xF, 16));
sb.append(Character.forDigit(b & 0xF, 16));
}
return sb.toString();
} catch (NoSuchAlgorithmException e) {
// SHA-256 is mandated by the JLS; this is unreachable.
throw new IllegalStateException("SHA-256 unavailable", e);
}
}
The SaasTenantResolver requires the following configuration at startup.
| Name | Type | Default | Required | Description |
|---|---|---|---|---|
gatewayBaseDomain | String | "" | No | The base domain of the gateway, used for subdomain-based tenant resolution. If empty or not provided, subdomain resolution is disabled, and the resolver relies exclusively on path-based resolution. |
The SaasTenantResolver implements the TenantResolver interface.
resolve(TenantResolutionContext context)This is the primary method used for processing live requests. It performs the full resolution and authentication flow.
TenantResolutionContext - An object containing the request's Host header, path, and API key.UUID - The unique identifier of the authenticated tenant.TenantAuthenticationException if any validation check fails. See the Troubleshooting section for details on failure reasons.resolve()This method is not used for live request processing in the SaaS environment.
WARNING
Calling resolve() without a context will always fail by throwing a TenantAuthenticationException. This is a fail-safe design to prevent bypassing tenant isolation in code paths that neglect to provide the request context.
When a request cannot be authenticated, the SaasTenantResolver throws a TenantAuthenticationException with a specific reason code. API publishers can use this reason to diagnose failed requests.
Reason Code (Reason) | Triggering Condition |
|---|---|
UNRESOLVED_SLUG | 1. No tenant slug was found in the Host header subdomain or the /t/{slug} path prefix.<br/>2. The extracted slug does not map to any active tenant. |
MISSING_KEY | The X-S2R-Tenant-Key header was not provided or was empty. |
INVALID_KEY | The API key in X-S2R-Tenant-Key does not match any known API key. |
REVOKED_KEY | The API key is valid but its status is not active. |
SUSPENDED_TENANT | The tenant associated with the API key is not active (e.g., it has been suspended or soft-deleted). |
TENANT_MISMATCH | The tenant identified by the API key does not match the tenant identified by the slug in the Host or path. |
sha256Hex hashing logic for its own authentication path.