Skip to content

Authentication

How the gateway decides a caller is allowed in, and who that caller is. Part of the Configuration Guide.

stdapi.ai supports three sources for API key authentication, plus Amazon Cognito user pool tokens and per-tenant API keys.

API Key Sources

Configure exactly one source. If several are set, the first match in this precedence order is used and the others are ignored:

  1. Direct API keyAPI_KEY (highest precedence)
  2. SSM Parameter StoreAPI_KEY_SSM_PARAMETER
  3. Secrets ManagerAPI_KEY_SECRETSMANAGER_SECRET (lowest precedence)

The methods below are listed in that precedence order. SSM Parameter Store remains the recommended method for production.

Amazon Cognito user pool tokens (Method 4) are independent of the API key and can be accepted alongside it, or instead of it — AUTHENTICATION_MODE decides.

Conflicting Configuration

Only one combination is rejected at startup: API_KEY set together with a Secrets Manager source (API_KEY_SECRETSMANAGER_SECRET). Every other combination starts normally and is resolved silently by the precedence order above — the lower-precedence sources are never read.

No Authentication Warning

If neither an API key source nor a user pool is configured, the API accepts all requests without authentication and a security warning is logged at startup. This is suitable only for internal/private deployments.

Settings Summary

Authentication

Configure one API key source. If several are set, precedence is API_KEY → SSM Parameter Store → Secrets Manager — see Authentication:

Variable Default Description
API_KEY_SSM_PARAMETER None AWS Systems Manager Parameter Store path for API key (recommended)
API_KEY_SECRETSMANAGER_SECRET None AWS Secrets Manager secret name containing API key
API_KEY_SECRETSMANAGER_KEY api_key JSON key name within Secrets Manager secret
API_KEY None Direct API key value (not recommended for production)
TENANT_API_KEYS false Accept per-tenant API keys scoped by the tenant records in the shared DynamoDB table
TENANT_KEY_CACHE_SECONDS 60 Per-instance validation cache, which is also the revocation window
TENANT_KEY_SSM_PARAMETER_PREFIX /stdapi-ai/tenant-keys SSM prefix minted tenant keys are delivered under, once
TENANT_KEY_SSM_KMS_KEY_ID None KMS key encrypting the delivery parameters, instead of alias/aws/ssm
TENANT_AWS_CREDENTIALS false Let a tenant register a cross-account IAM role its model invocations run under
AUTHENTICATION_MODE any Accepted methods: any, api_key or cognito

Amazon Cognito user pool tokens are an alternative to the API key — see Amazon Cognito Authentication:

Variable Default Description
AWS_COGNITO_USER_POOL_ID None User pool whose tokens authenticate clients (enables the method)
AWS_COGNITO_CLIENT_IDS None App client IDs whose tokens are accepted (required with a pool)
AWS_COGNITO_REQUIRED_SCOPES None Scopes a token must all carry
AWS_COGNITO_ACCEPT_ID_TOKEN false Also accept identity tokens, not only access tokens
AWS_COGNITO_ISSUER_TYPE original Pool issuer configuration: original or updated

Publishing where tokens come from lets an AI agent authenticate itself — see Authentication Discovery:

Variable Default Description
OAUTH_RESOURCE_IDENTIFIER None Public URL clients dial (publishes the discovery document)
OAUTH_AUTHORIZATION_SERVERS The user pool issuer Issuer URLs of the authorization servers
OAUTH_SCOPES_SUPPORTED Required scopes Scopes a token needs, advertised to clients

Method 1: Direct API Key

Provide the API key directly via environment variable. Intended for local development and testing; it takes precedence over both AWS-backed sources.

API_KEY

Purpose : Static API key value

Security Warning : Avoid hardcoding in configuration files; use environment variables only

Client Usage : Clients must include this key in the Authorization: Bearer <key> header or X-API-Key header

export API_KEY=sk-1234567890abcdef...

Recommended - Use AWS Systems Manager Parameter Store for secure key storage with encryption, access control, and auditing. This method should be used only with already existing parameters.

API_KEY_SSM_PARAMETER

Purpose : Name of the SSM parameter containing the API key. The parameter is retrieved from the current region detected by the running container, or defaults to the first region in AWS_BEDROCK_REGIONS.

Recommendation : Use SecureString type for encryption at rest

IAM Permissions Required : ssm:GetParameter, kms:Decrypt (if encrypted)

export API_KEY_SSM_PARAMETER=/stdapi/prod/api-key

Method 3: Secrets Manager

Use AWS Secrets Manager for secure key storage with automatic rotation support. This method should be used only with already existing secrets.

API_KEY_SECRETSMANAGER_SECRET

Purpose : Name of the Secrets Manager secret containing the API key. The secret is retrieved from the current region detected by the running container, or defaults to the first region in AWS_BEDROCK_REGIONS.

Format : Can be a plain string or JSON object

IAM Permissions Required : secretsmanager:GetSecretValue

API_KEY_SECRETSMANAGER_KEY

Purpose : JSON key name within the secret (if the secret is a JSON object)

Default : api_key

Plain String Secret:

export API_KEY_SECRETSMANAGER_SECRET=stdapi-api-key

JSON Secret:

export API_KEY_SECRETSMANAGER_SECRET=stdapi-credentials
export API_KEY_SECRETSMANAGER_KEY=api_key

Example JSON secret structure:

{
  "api_key": "sk-1234567890abcdef...",
  "other_config": "value"
}

Method 4: Amazon Cognito User Pool Tokens

Accept the bearer tokens issued by an Amazon Cognito user pool instead of, or alongside, the API key. Each caller gets its own short-lived credential, and the verified caller is the identity per-user cost attribution bills against; withdrawing a caller's access takes effect when their current token expires. Clients send the token in the Authorization: Bearer <token> or X-API-Key header, like an API key. What is validated on every request is described in Authentication & Security.

export AWS_COGNITO_USER_POOL_ID=eu-west-3_a1b2c3d4e
export AWS_COGNITO_CLIENT_IDS=1example23456789abcdefghij

Incomplete configuration fails startup

A user pool without AWS_COGNITO_CLIENT_IDS, a Cognito setting without a pool, or an AUTHENTICATION_MODE that contradicts what is configured, all stop the server at startup with an explicit message — a partially configured pool never degrades into an unauthenticated deployment.

The pool also configures agent discovery

Add OAUTH_RESOURCE_IDENTIFIER and an AI agent can authenticate itself against the deployment. Nothing else is needed: the pool's issuer and required scopes are what get published — see Authentication Discovery for Agents.

AUTHENTICATION_MODE

Purpose : Which client authentication methods the deployment accepts

Default : any — every method that is configured

Values : - any: the API key, tenant API keys and user pool tokens, whichever is configured - api_key: the API key and tenant API keys only; startup fails if a user pool is also configured - cognito: user pool tokens only; startup fails if an API key source or tenant API keys are also configured

export AUTHENTICATION_MODE=cognito

AWS_COGNITO_USER_POOL_ID

Purpose : Identifier of the user pool whose tokens authenticate clients. Setting it enables the method; the pool's AWS Region is read from the identifier itself, and the public signing keys are loaded from that Region at startup.

Default : None — user pool tokens are not accepted

Requirement : AWS_COGNITO_CLIENT_IDS must be set too

IAM Permissions Required : None — the signing keys are public

export AWS_COGNITO_USER_POOL_ID=eu-west-3_a1b2c3d4e

AWS_COGNITO_CLIENT_IDS

Purpose : Comma-separated app client IDs whose tokens are accepted. A token issued to any other app client of the pool is rejected.

Default : Empty — startup fails when a user pool is configured without it

Requirement : Required whenever AWS_COGNITO_USER_POOL_ID is set

export AWS_COGNITO_CLIENT_IDS=1example23456789abcdefghij,2example3456789abcdefghijk

AWS_COGNITO_REQUIRED_SCOPES

Purpose : Comma-separated OAuth 2.0 scopes a token must all carry to be accepted

Default : None — any scope set is accepted

Requirement : Custom scopes exist only on tokens issued by the pool's OAuth 2.0 token endpoint, which needs a resource server and a pool domain. Tokens obtained by signing in with a username and password carry only aws.cognito.signin.user.admin and are rejected when a custom scope is required.

export AWS_COGNITO_REQUIRED_SCOPES=stdapi/invoke

AWS_COGNITO_ACCEPT_ID_TOKEN

Purpose : Also accept identity tokens, not only access tokens

Default : false

Effect : Identity tokens describe the signed-in user rather than granting API access, and carry no scopes. Enable only for clients that cannot obtain an access token.

export AWS_COGNITO_ACCEPT_ID_TOKEN=true

AWS_COGNITO_ISSUER_TYPE

Purpose : The pool's issuer configuration, which decides the issuer URL its tokens carry

Default : original

Values : - original: https://cognito-idp.<region>.amazonaws.com/<pool-id> - updated: https://issuer-cognito-idp.<region>.amazonaws.com/<pool-id>, available on the Essentials and Plus pool tiers

Requirement : Must match the pool's own setting; tokens whose issuer differs are rejected

export AWS_COGNITO_ISSUER_TYPE=updated

Method 5: Tenant API Keys

Accept per-tenant API keys (sk-std-...), each backed by a record in the shared DynamoDB table that scopes what the key may call — model allow/deny lists and endpoint restrictions. The records are declared by the operator (the Terraform module's tenants variable, or written directly); the secret is minted by the server and delivered once through SSM Parameter Store. How keys are issued, scoped, cached and revoked is described in Authentication & Security. Clients send the key in the Authorization: Bearer <key> or X-API-Key header, like any API key.

export AWS_DYNAMODB_TABLE=stdapi-ai
export TENANT_API_KEYS=true

TENANT_API_KEYS

Purpose : Enable per-tenant API keys, validated against the tenant records in the shared DynamoDB table

Default : false — tenant-shaped credentials are only compared against the deployment API key, like any other value

Requirement : AWS_DYNAMODB_TABLE must be set, or startup fails. TENANT_KEY_SSM_PARAMETER_PREFIX has a default and needs no configuration of its own — override it if this deployment shares an AWS account with another

IAM Permissions Required : The shared table permissions, plus ssm:PutParameter and ssm:GetParameter on the delivery prefix — see Tenant API Key Delivery

TENANT_KEY_CACHE_SECONDS

Purpose : Seconds each server instance caches a validated tenant key before re-reading its records. This is the revocation window: a key revoked, disabled or re-scoped keeps its previous decision for up to this long per instance

Default : 60

Values : 0 disables the cache and reads the table on every request

TENANT_KEY_SSM_PARAMETER_PREFIX

Purpose : SSM Parameter Store prefix minted tenant keys are delivered under, one SecureString parameter named <prefix>/<key id> per tenant

Default : /stdapi-ai/tenant-keys

Security Warning : Use a prefix private to this deployment: any principal allowed to read under it can read every tenant's key, so a deployment sharing an AWS account with another must not keep the default. Retrieve each key once, deliver it, then delete the parameter

export TENANT_KEY_SSM_PARAMETER_PREFIX=/stdapi-ai/prod/tenant-keys

TENANT_KEY_SSM_KMS_KEY_ID

Purpose : AWS KMS key encrypting the SecureString parameters the minted tenant keys are delivered through

Default : None — the parameters are encrypted with the AWS-managed alias/aws/ssm key, whose key policy lets any principal of the account holding ssm:GetParameter under the prefix decrypt them

Values : A key ID, an alias (alias/<name>), or an ARN of either

Effect : Reading a delivered key then also requires kms:Decrypt on that key, so the delivery is protected by a key policy of your own instead of the account-wide reach of the AWS-managed key

IAM Permissions Required : kms:Encrypt and kms:Decrypt on the key (add kms:GenerateDataKey if the account's default parameter tier creates advanced parameters) — see Tenant API Key Delivery

Set for you by the Terraform module

The Terraform module passes the deployment's own KMS key here automatically; there is nothing to configure.

export TENANT_KEY_SSM_KMS_KEY_ID=alias/stdapi-ai

TENANT_AWS_CREDENTIALS

Purpose : Let a tenant register an IAM role of its own AWS account (aws_role_arn on its tenant record), so that tenant's model invocations run under the tenant's account — its Amazon Bedrock model access, quotas and bill. The server assumes the role with a server-minted ExternalId (the AWS confused-deputy pattern); no secret is stored anywhere. See Tenant AWS credentials

Default : false — every request runs under the server's own identity, and a tenant record declaring aws_role_arn is refused rather than silently billed to the deployment

Requirement : Requires TENANT_API_KEYS. Incompatible with Amazon Bedrock Guardrails (AWS_BEDROCK_GUARDRAIL_IDENTIFIER or a model-alias guardrail): a guardrail of this deployment's account cannot be evaluated by a tenant principal, so startup fails rather than serving tenant requests unguarded

Required IAM Permissions : sts:AssumeRole on the tenant roles — see IAM permissions

export TENANT_AWS_CREDENTIALS=true

Authentication Discovery for Agents

Publishes, at /.well-known/oauth-protected-resource, where clients obtain a token, and points every 401 Unauthorized at that document. An AI agent — or any MCP client — can then authenticate against this deployment without having been configured for it first. See Authentication Discovery for Agents for the full flow.

Nothing is published until OAUTH_RESOURCE_IDENTIFIER is set. With an Amazon Cognito user pool configured, that variable is the only one to set: the pool already names the issuer and the scopes, and both are published from it. The document is public and unauthenticated, since a client reads it before it has any credential.

OAUTH_RESOURCE_IDENTIFIER

Purpose : Public URL clients use to reach this deployment, published as the identity of the protected resource

Default : None — no discovery document is published, and 401 responses only state that a bearer token is expected

Requirement : Must be the exact origin clients dial — scheme and host, an explicit port only when it is not the default one for the scheme, and no path, query or fragment. Clients compare it character by character against the URL they used, so https://api.example.com and https://api.example.com:443 are not interchangeable. Requires OAUTH_AUTHORIZATION_SERVERS, unless a user pool supplies the issuer.

export OAUTH_RESOURCE_IDENTIFIER=https://api.example.com

OAUTH_AUTHORIZATION_SERVERS

Purpose : Issuer URLs of the OAuth 2.0 authorization servers that issue tokens for this deployment, comma-separated

Default : The issuer of the AWS_COGNITO_USER_POOL_ID pool, when one is configured — otherwise none

Effect : A client reads each issuer's own metadata to find where to sign in, so this deployment never describes the sign-in flow itself. A load balancer or API gateway authenticating in front of stdapi.ai publishes the issuer of whichever provider it uses.

With a user pool configured, leave this unset: the pool issues the tokens the deployment accepts, so its own issuer is published — `https://cognito-idp.<region>.amazonaws.com/<pool-id>`, or `https://issuer-cognito-idp.<region>.amazonaws.com/<pool-id>` when [`AWS_COGNITO_ISSUER_TYPE`](#aws-cognito-issuer-type) is `updated`. `<region>` and `<pool-id>` come from the pool ID itself, and the host follows the pool Region's AWS partition (`amazonaws.com.cn` in China, `amazonaws.eu` in the European Sovereign Cloud). Set the variable only to publish further issuers.

Requirement : Each entry is an https URL with no query or fragment. Required when OAUTH_RESOURCE_IDENTIFIER is set and no user pool is configured. When one is, the list must include the pool's own issuer — a client sent anywhere else obtains a token every request refuses, so startup fails instead.

export OAUTH_AUTHORIZATION_SERVERS=https://cognito-idp.eu-west-3.amazonaws.com/eu-west-3_a1b2c3d4e

OAUTH_SCOPES_SUPPORTED

Purpose : Scopes a token needs to call this API, comma-separated

Default : AWS_COGNITO_REQUIRED_SCOPES — with neither set, no scope is advertised and a client asks for whatever its own configuration names

Effect : Advertised both in the discovery document and in the 401 challenge, so a client asks its authorization server for the right scopes on its first attempt. The scopes a token must carry to be accepted are exactly the scopes to ask for, so they are published unless this variable names others.

export OAUTH_SCOPES_SUPPORTED=stdapi/invoke