Subchapter 25.1
references/api-authorization.mdMarkdown5 KBView on GitHub
This reference covers wiring a Cognito user pool to an Amazon API Gateway API (and ALB) so only
authenticated requests get through. It covers only the authorizer side. Creating the API’s
routes, integrations, and backing Lambda belongs to the aws-serverless skill.
| API type | Authorizer | Token used |
|---|---|---|
| HTTP API (API Gateway v2) | Built-in JWT authorizer | ID or access token |
| REST API (API Gateway v1) | Cognito user pools authorizer (COGNITO_USER_POOLS) | ID token by default |
| Application Load Balancer | ALB built-in authenticate-cognito action | Performs the OIDC login itself |
The JWT authorizer validates the token’s signature, issuer, and audience with no Lambda.
aws apigatewayv2 create-authorizer \
--api-id <api-id> \
--authorizer-type JWT \
--name cognito-jwt \
--identity-source '$request.header.Authorization' \
--jwt-configuration Audience=<app-client-id>,Issuer=https://cognito-idp.<region>.amazonaws.com/<pool-id>https://cognito-idp.<region>.amazonaws.com/<userPoolId>.aud = client id; the access token carries
client_id and scope. If you send access tokens, the JWT authorizer still validates against
the configured audience/issuer — send the token type your configuration expects and, for
scope-based authorization, use the access token.The HTTP-API JWT authorizer validates only the signature, iss, aud / client_id,
exp/nbf/iat, and — if you set authorizationScopes on the route — the scope /
scp claim. It does not enforce arbitrary custom claims like custom:tenant_id,
cognito:groups, or attributes added by a pre-token-generation Lambda. Those must be
inspected in the backend / integration.
API Gateway forwards the JWT claims into the request context. In a Lambda integration:
// event.requestContext.authorizer.jwt.claims is a flat map of every claim in the token
const tenantId = event.requestContext.authorizer.jwt.claims["custom:tenant_id"];
const groups = event.requestContext.authorizer.jwt.claims["cognito:groups"]; // string or array
if (tenantId !== requestedTenant) {
return { statusCode: 403, body: "wrong tenant" };
}For heavier claim-based policy, use a Lambda authorizer (any custom logic) or Amazon Verified Permissions (Cedar policies over token claims) instead.
aws apigateway create-authorizer \
--rest-api-id <api-id> \
--name cognito-authorizer \
--type COGNITO_USER_POOLS \
--provider-arns arn:aws:cognito-idp:<region>:<account>:userpool/<pool-id> \
--identity-source method.request.header.AuthorizationThen set the method’s authorizationType to COGNITO_USER_POOLS and reference the authorizer.
REST API Cognito authorizers validate the ID token by default.
If your backend isn’t behind an API Gateway authorizer, validate the JWT on every request:
https://cognito-idp.<region>.amazonaws.com/<pool-id>/.well-known/jwks.json.kid.iss (the issuer URL above), exp, token_use (id vs access), and
aud/client_id.Use a maintained JWT library (e.g. aws-jwt-verify) rather than hand-rolling verification.
The authorizer authenticates callers but is not the whole story:
| Error | Cause | Fix |
|---|---|---|
| 401 with a valid token | Issuer or audience mismatch, or wrong token type | Set issuer/audience exactly as above; send the token the authorizer expects |
| 401 “Unauthorized” but token looks fine | identity-source header not sent or wrong casing | Send Authorization: <token>; match the configured identity source |
| Works locally, 403 in prod | CORS preflight blocked (OPTIONS needs no auth) | Allow unauthenticated OPTIONS; configure CORS on the API |
aws-serverless skill for API Gateway routes/integrations and Lambda implementation.