Skip to content

Authentication API (`auth.arda.cards`)

The Authentication API gives Arda applications a stable, Arda-owned HTTPS surface for interactive login and tenant selection. Each partition receives its own endpoint under the auth.arda.cards domain family, for example https://dev.alpha002.auth.arda.cards.

The first release exposes password login and tenant switching.

Arda clients previously depended directly on Cognito-specific authentication calls and configuration. Tenant selection also required changing a Cognito attribute and refreshing tokens in the correct order. Repeating those details in every client application would expose the Cognito client secret, couple clients to AWS APIs, and subject the client implementation to complicated call sequences.

The platform needs one small authentication facade that:

  • keeps the Cognito web-client secret in AWS Secrets Manager;
  • presents a stable, partition-specific Arda URL;
  • performs login and tenant-token rotation consistently;
  • leaves tenant authorization with the domain component that owns current AgentFor membership.

A client can authenticate with username and password, then select a tenant and receive replacement access and ID tokens containing that tenant context. The client does not need AWS credentials, a Cognito client secret, or direct access to the Cognito SDK.

The active-tenant claim is context, not proof of access. A user may name a tenant during token rotation, but every business component must validate the user’s current AgentFor relationship when serving a tenant-scoped request. This keeps authorization current when a user is suspended or evicted, even if an older JWT has not expired.

The initial capability includes:

  • POST /v1/login using Cognito USER_PASSWORD_AUTH;
  • POST /v1/tenantSwitch using a bearer access token and refresh token;
  • a regional API Gateway HTTP API with a custom DNS name and no public execute-api endpoint;
  • one Lambda function per operation;
  • request validation derived from the OpenAPI contract;
  • a CloudFormation export containing the partition’s authentication base URL;
  • unit and CDK synthesis tests for handlers, routes, DNS, permissions, and configuration.
  1. Every enabled partition exposes a TLS-protected endpoint at https://<partition>.<infrastructure>.auth.arda.cards.
  2. Login returns Cognito’s authentication result or challenge without exposing the web-client secret.
  3. Tenant switching rejects malformed requests, invalid tokens, and token pairs that identify different users.
  4. Successful tenant switching returns a token set generated after custom:tenant is updated.
  5. The source OpenAPI contract drives TypeScript types, runtime validation, and CDK route definitions.
  6. Downstream APIs continue to validate current tenant membership at access time.

The Authentication API OpenAPI specification is the source of truth for paths and schemas.

OperationAuthenticationRequestResult
POST /v1/loginNoneAuthFlow, USERNAME, PASSWORDCognito authentication result or challenge
POST /v1/tenantSwitchAccess token in Authorization headerTenantId, RefreshTokenReplacement access, ID, and refresh tokens

Only USER_PASSWORD_AUTH is accepted for login. Both request schemas reject unknown properties and require non-empty credential and tenant values. Responses use JSON and Cache-Control: no-store; contract failures return 400, while invalid or mismatched tenant-switch credentials return 401.

The login Lambda validates the JSON request, loads and caches the Cognito web-client secret from Secrets Manager, and computes Cognito’s SECRET_HASH from the username, client ID, and client secret. It then calls InitiateAuth(USER_PASSWORD_AUTH).

Successful authentication results are returned unchanged. Cognito challenges are also passed through so a client can detect a challenge without the facade inventing a second authentication state model. Recognized Cognito service errors retain their HTTP status and error name; unexpected failures remain unhandled so Lambda and API Gateway record them as operational faults.

Tenant switching deliberately proves token identity before changing the active tenant:

  1. Parse the bearer access token and the OpenAPI-validated request body.
  2. Call Cognito GetUser and verify the access token’s token_use, client_id, issuer, subject, and username against the partition’s user pool and web client.
  3. Use the supplied refresh token once and validate the resulting access token.
  4. Require the refresh-token subject to equal the bearer-token subject.
  5. Update the Cognito user’s custom:tenant attribute.
  6. Refresh a second time so the pre-token-generation trigger copies the new tenant into the returned token claims.

The first refresh prevents one user’s bearer token from being paired with another user’s refresh token. The second refresh is required because tokens created before the attribute update contain the old tenant context.

Tenant membership is intentionally not checked in this flow. Membership can change at any time, whereas the token is a time-bounded credential. Treating custom:tenant as a selector and rechecking AgentFor in each component prevents a stale token from preserving access after eviction. See Access Rules for UserAccounts and Tenants and User-Tenant Onboarding and Membership for the domain authorization model.

For each partition, Route 53 directs the Arda-owned hostname to a regional API Gateway custom domain secured by an ACM certificate. API Gateway dispatches the two routes to dedicated Node.js Lambdas; the Lambdas use Cognito for identity operations, Secrets Manager for the web-client secret, and CloudWatch Logs for short-retention operational logs.

PlantUML diagram

The DNS hierarchy separates infrastructure and partition concerns. The infrastructure ingress stack owns the <infrastructure>.auth.arda.cards hosted zone and wildcard certificate. The partition authentication stack creates the partition record, API custom domain, routes, Lambdas, and the <infrastructure>-<partition>-API-AuthBaseUrl CloudFormation export.

  • API Gateway’s default execute-api endpoint is disabled, forcing use of the managed hostname and certificate.
  • Lambda IAM access is narrow: both functions can read the web-client secret, and only the tenant-switch function can call cognito-idp:AdminUpdateUserAttributes on its user pool.
  • Responses containing credentials are marked no-store.
  • The client secret is cached in each warm Lambda environment but is never sent to the caller.
  • Access-token validation checks Cognito state with GetUser as well as token claims. The downstream API remains responsible for JWT signature validation and tenant authorization on business requests.
  • Function log groups use one-week retention and are deleted with the stack.

The existing AuthnServiceConstruct creates the Cognito user pool, resource server, web client, machine-to-machine client, and their Secrets Manager secrets. When both an authentication hosted zone and certificate ARN are supplied, it now composes a LoginApi construct.

LoginApi creates the regional custom domain and HTTP API, disables the default endpoint, creates the two Lambda integrations, grants their permissions, and writes the Route 53 alias. The containing PartitionAuthn stack publishes the base URL beside the existing user-pool, web-client, and M2M exports. Requiring the hosted zone and certificate together prevents a partially configured public endpoint.

src/main/openapi/auth-api.yaml is an application contract, not a CloudFormation input. The generator reads it and produces three artifacts used by the TypeScript implementation:

  • request and response types through openapi-typescript;
  • JSON Schema definitions consumed by Ajv for runtime request validation;
  • an operation map that supplies the HTTP methods and paths to CDK.

The generated directory is build output and is not committed. Build, test, and CDK synthesis jobs generate the contract first, check it, and then compile the code. This makes an OpenAPI edit fail early if required schemas or operation IDs are missing and prevents route strings, runtime validation, and client-facing documentation from drifting independently.

Handler unit tests cover valid requests, malformed JSON, missing and additional properties, Cognito errors, bearer parsing, token-claim validation, subject mismatch, and the two-refresh tenant-switch sequence. CDK tests assert route mapping, the custom domain, the Route 53 alias, IAM grants, paired zone/certificate configuration, and publication of the authentication base URL.

Before rollout, synthesize every partition and verify DNS delegation, certificate validation, the exported base URL, and both operations against a non-production user. Authorization verification must include deleting or suspending an AgentFor relationship and confirming that downstream access is denied even when the caller still holds a token naming that tenant.


Copyright: (c) Arda Systems 2025-2026, All rights reserved