I don't understand the following behavior of my API Gateway and Cognito User Pool Authorizer. Using the AWS SAM template, I have deployed the following Cognito User Pool:
CognitoUserPoolClient:
Type: AWS::Cognito::UserPoolClient
Properties:
UserPoolId: !Ref CognitoUserPool
ClientName: !Sub CognitoUserPoolClient-${StageName}
GenerateSecret: false
SupportedIdentityProviders:
- COGNITO
ExplicitAuthFlows:
- ALLOW_USER_PASSWORD_AUTH
- ALLOW_CUSTOM_AUTH
- ALLOW_REFRESH_TOKEN_AUTH
- ALLOW_USER_SRP_AUTH
OptalXCognitoUserPool:
Type: AWS::Cognito::UserPool
Properties:
AutoVerifiedAttributes:
- email
EmailVerificationSubject: Verify your email.
EmailVerificationMessage: Please click the link below to verify your email address. {####}
EmailConfiguration:
EmailSendingAccount: DEVELOPER
From: Widgets Co <admin@widgets.com>
ReplyToEmailAddress: admin@widgets.com
SourceArn: !FindInMap [ StageMap, !Ref StageName, EmailSESArn ]
UserPoolName: !Sub CognitoUserPool-${StageName}
VerificationMessageTemplate:
DefaultEmailOption: CONFIRM_WITH_LINK
AdminCreateUserConfig:
AllowAdminCreateUserOnly: true
UnusedAccountValidityDays: 90
InviteMessageTemplate:
EmailMessage: ...
EmailSubject: Welcome to Widgets Co!
SMSMessage: Your username is {username} and temporary password is {####}.
Policies:
PasswordPolicy:
MinimumLength: 8
UsernameAttributes:
- email
Schema:
- AttributeDataType: String
Name: user_id
Mutable: false
DeveloperOnlyAttribute: false
- Name: email
Mutable: false
Required: true
Then, I've defined an API Gateway that uses the Cognito User Pool as its Authorizer:
WidgetsAPI:
Type: 'AWS::Serverless::Api'
Name: WidgetsAPI
Properties:
GatewayResponses:
EXPIRED_TOKEN:
ResponseParameters:
Headers:
Fail-Reason: "'Expired token'"
Access-Control-Allow-Origin: "'*'"
DEFAULT_4xx:
ResponseParameters:
Headers:
Access-Control-Expose-Headers: "'WWW-Authenticate'"
Access-Control-Allow-Origin: "'*'"
DEFAULT_5XX:
ResponseParameters:
Headers:
Fail-Reason: "'Internal server error. Check logs.'"
Access-Control-Allow-Origin: "'*'"
Auth:
DefaultAuthorizer: CognitoAuthorizer
Authorizers:
CognitoAuthorizer:
UserPoolArn: !FindInMap [ StageMap, !Ref StageName, UserPoolArn ]
AddDefaultAuthorizerToCorsPreflight: false
Here my UserPoolArn
is inside a Mappings
that differs based on the environment we are in (stage
or prod
). I've defined a bunch of endpoints, but for the sake of demonstration, there's on called /users
.
Now when I pass in a GET /prod/users
request, with a header of Authorization: THE_ID_TOKEN_FROM_MY_COGNITO_SIGN_IN
, I receive a 401 Unauthorized
. I can 100% confirm that I am using the right ID token. For instance, when I pass in the same ID token to test via the management console's authorizer screen, I get a 200
response:
Moreover, I've deployed in our company's stack multiple API endpoints, all with the same pattern - we've been using id_token
for the past year quite successfully. The only difference is we are trying to move our managed user pools into CloudFormation templates, so the most recent User Pool (including this problematic one) were created via aws sam build
.
Why is this happening? I know I may not have provided enough details, but happy to add any other pieces of code or configs that are relevant. It's hard for me to reproduce this error since in all my other environments (stage
, my other microservices API endpoints), the API Gateway and Cognito User Pool work perfectly after being deployed via AWS SAM.
In fact, I've deployed the exact same version of this infrastructure in our staging environment, and it works perfectly (the only difference between our stage and prod environments is the Stage
parameter that we pass into our template YAML stack).
I've found that if I go into the management console and manually deploy the API again, everything works great (I get a 200
response with the expected response output):
Note - there's several posts (here, here, and here) about this topic, but none of them describe my exact problem, which is why my API Gateway returns 401 Unauthorized
errors upon first applying my Cloudformation stack, and only works properly after I manually deploy from the console the entire API Gateway stage.