Aws sam cognito api gateway - access token forbidden but works if it's from postman - amazon-web-services

I have a CognitoUserPool and a lambda function that requires an authenticated user.
When making a request with the token acquired from postman that opens the aws UI login, it works, but when using the token from a curl login it doesn't got 403 forbidden, any idea of what I'm missing?
My template.yaml
AWSTemplateFormatVersion: '2010-09-09'
Parameters:
Env:
Type: String
Default: dev
AllowedValues:
- dev
- test
- prod
Description: >-
sam-app
Transform:
- AWS::Serverless-2016-10-31
Globals:
Function:
Timeout: 100
Runtime: nodejs16.x
MemorySize: 128
Resources:
CognitoUserPool:
Type: AWS::Cognito::UserPool
Properties:
UserPoolName: !Sub ${Env}-Cognito-User-Pool
Policies:
PasswordPolicy:
MinimumLength: 8
UsernameAttributes:
- email
AutoVerifiedAttributes:
- email
Schema:
- AttributeDataType: String
Name: email
Required: false
CognitoUserPoolClient:
Type: AWS::Cognito::UserPoolClient
Properties:
UserPoolId: !Ref CognitoUserPool
ClientName: !Sub ${Env}-CognitoUserPoolClient
GenerateSecret: false
CallbackURLs:
- http://localhost:3000
LogoutURLs:
- http://localhost:3000
AllowedOAuthFlowsUserPoolClient: true
ExplicitAuthFlows:
- ALLOW_ADMIN_USER_PASSWORD_AUTH
- ALLOW_USER_PASSWORD_AUTH
- ALLOW_CUSTOM_AUTH
- ALLOW_USER_SRP_AUTH
- ALLOW_REFRESH_TOKEN_AUTH
- ALLOW_USER_PASSWORD_AUTH
AllowedOAuthFlows:
- code
- implicit
SupportedIdentityProviders:
- COGNITO
AllowedOAuthScopes:
- openid
- email
- profile
CognitoDomainName:
Type: AWS::Cognito::UserPoolDomain
Properties:
Domain: !Sub ${Env}-domain-test
UserPoolId: !Ref CognitoUserPool
HttpApi:
Type: AWS::Serverless::HttpApi
DependsOn: CognitoUserPoolClient
Properties:
StageName: !Ref Env
Auth:
Authorizers:
CustomCognitoAuthorizer:
UserPoolArn: !GetAtt CognitoUserPool.Arn
AuthorizationScopes:
- email
IdentitySource: "$request.header.Authorization"
JwtConfiguration:
issuer: !Sub https://cognito-idp.${AWS::Region}.amazonaws.com/${CognitoUserPool}
audience:
- !Ref CognitoUserPoolClient
CorsConfiguration:
AllowMethods:
- GET
AllowHeaders: '*'
AllowOrigins:
- '*'
getAllItemsFunction:
Type: AWS::Serverless::Function
Properties:
Handler: src/handlers/get-all-items.getAllItemsHandler
Events:
DosGet:
Type: HttpApi
Properties:
Auth:
Authorizer: CustomCognitoAuthorizer
Path: /
ApiId: !Ref HttpApi
Method: GET
The curl that I use to login, got it from this post AWS - Cognito Authentication - Curl Call - Generate Token Without CLI - No Client Secret
Method: POST
Endpoint: https://cognito-idp.{REGION}.amazonaws.com/
Content-Type: application/x-amz-json-1.1
X-Amz-Target: AWSCognitoIdentityProviderService.InitiateAuth
Body:
{
"AuthParameters" : {
"USERNAME" : "YOUR_USERNAME",
"PASSWORD" : "YOUR_PASSWORD"
},
"AuthFlow" : "USER_PASSWORD_AUTH", // Don't have to change this if you are using password auth
"ClientId" : "APP_CLIENT_ID"
}

After a bit of digging, I solved the issue, by analyzing the token that was generated from each method I saw a difference.
The token acquired from the aws UI.
{
"scope": "aws.cognito.signin.user.admin"
}
And the one from curl login
{
"scope": "openid profile email"
}
So the solution was to add aws.cognito.signin.user.admin as part of my UserPoolClient AllowedOAuthScopes
AllowedOAuthScopes:
- openid
- email
- profile
- aws.cognito.signin.user.admin
and on my HttpApi AuthorizationScopes
AllowedOAuthScopes:
- email
- aws.cognito.signin.user.admin

Related

Serverless Framework How to Get Access, Id and Refresh Tokens from AWS Cognito

I am trying to secure my serverless NodeJS apis using AWS Cognito User Pools.
Below is a sample of my serverless framework configuration:
service: hello-world
frameworkVersion: '3'
provider:
name: aws
runtime: nodejs14.x
environment:
user_pool_id: { Ref: UserPool }
client_id: { Ref: UserClient }
iam:
role:
statements:
- Effect: Allow
Action:
- cognito-idp:AdminInitiateAuth
- cognito-idp:AdminCreateUser
- cognito-idp:AdminSetUserPassword
Resource: "*"
functions:
loginUser:
handler: ./auth/login.handler
events:
- http:
path: auth/login
method: post
cors: true
signupUser:
handler: ./auth/signup.handler
events:
- http:
path: auth/signup
method: post
cors: true
list:
handler: ./users/users.handler
events:
- http:
path: users/list
method: get
cors: true
authorizer:
type: COGNITO_USER_POOLS
authorizerId:
Ref: ApiGatewayAuthorizer
resources:
Resources:
ApiGatewayAuthorizer:
Type: AWS::ApiGateway::Authorizer
Properties:
Name: CognitoUserPool
Type: COGNITO_USER_POOLS
IdentitySource: method.request.header.Authorization
RestApiId:
Ref: ApiGatewayRestApi
ProviderARNs:
- Fn::GetAtt:
- UserPool
- Arn
UserPool:
Type: AWS::Cognito::UserPool
Properties:
UserPoolName: serverless-auth-pool
Schema:
- Name: email
Required: true
Mutable: true
Policies:
PasswordPolicy:
MinimumLength: 6
AutoVerifiedAttributes: ["email"]
UserClient:
Type: AWS::Cognito::UserPoolClient
Properties:
ClientName: user-pool-ui
GenerateSecret: false
UserPoolId: { Ref: UserPool }
AccessTokenValidity: 1
IdTokenValidity: 1
ExplicitAuthFlows:
- "ADMIN_NO_SRP_AUTH"
I can successfully can call the signup and login endpoints to get a token and then use this token as an Authorization header to call my /users/list endpoint to get a list of users.
My problem is that I was expecting the login endpoint to return 3 tokens - an id token, an access token and a refresh token.
The login endpoint currently only returns one token that has a claim of:
"token_use": "id"
If I pass this token to the /users/list api then it is successfully validated, but I thought that the api would need the access token instead of the id token for authentication.
Does anyone know if my assumption is correct and how to fix the issue or have I misunderstood how the auth flow works ?

Why does Cognito IDP initiate_auth not authorise API Gateway in this setup?

I have a cloud formation stack containing a Cognito User Pool and its client, an API Gateway and an Authorizer.
If I log in using the hosted form I get JWT tokens - and the access_token successfully authorises the API.
However if I log using idp.initiate_auth(), using the same credentials, I also get a set of JWT tokens - but the access_token won't allow me to access the API.
I can see that the hosted form does some opaque magic inside a function called getAdvancedSecurityData() and writes a form variable called cognitoAsfData... but beyond that I cannot make sense of it.
Here is - I hope the relevant sections of CFN config:
AWSTemplateFormatVersion: "2010-09-09"
Description: ""
Resources:
LADApiGatewayRestApi:
Type: "AWS::ApiGateway::RestApi"
Properties:
Name: "DemoApi"
Description: "This is the demo API."
ApiKeySourceType: "HEADER"
EndpointConfiguration:
Types:
- "REGIONAL"
LADApiGatewayStage:
Type: "AWS::ApiGateway::Stage"
Properties:
StageName: "test"
DeploymentId: !Ref LADApiGatewayDeployment
RestApiId: !Ref LADApiGatewayRestApi
CacheClusterEnabled: false
TracingEnabled: false
LADApiGatewayDeployment:
Type: "AWS::ApiGateway::Deployment"
Properties:
RestApiId: !Ref LADApiGatewayRestApi
DependsOn:
- LADApiGatewayResource
- LADApiGatewayMethod
- LADApiGatewayRestApi
LADApiGatewayResource:
Type: "AWS::ApiGateway::Resource"
Properties:
RestApiId: !Ref LADApiGatewayRestApi
PathPart: "transactions"
ParentId: !GetAtt LADApiGatewayRestApi.RootResourceId
LADApiGatewayMethod:
Type: "AWS::ApiGateway::Method"
Properties:
RestApiId: !Ref LADApiGatewayRestApi
ResourceId: !Ref LADApiGatewayResource
HttpMethod: "GET"
AuthorizationType: "COGNITO_USER_POOLS"
AuthorizerId: !Ref LADApiGatewayAuthorizer
ApiKeyRequired: false
MethodResponses:
-
ResponseModels:
"application/json": "Empty"
StatusCode: "200"
Integration:
CacheNamespace: !Ref LADApiGatewayResource
ContentHandling: "CONVERT_TO_TEXT"
IntegrationHttpMethod: "POST"
IntegrationResponses:
-
ResponseTemplates: {}
StatusCode: "200"
PassthroughBehavior: "WHEN_NO_MATCH"
TimeoutInMillis: 29000
Type: "AWS"
Uri: !Sub "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:${LADLambdaFunction}/invocations"
AuthorizationScopes:
- "email"
LADApiGatewayAuthorizer:
Type: "AWS::ApiGateway::Authorizer"
Properties:
RestApiId: !Ref LADApiGatewayRestApi
Name: "LADDemoApiAuthorizer"
Type: "COGNITO_USER_POOLS"
ProviderARNs:
- !GetAtt LADCognitoUserPool.Arn
AuthType: "cognito_user_pools"
IdentitySource: "method.request.header.Authorization"
LADCognitoUserPool:
Type: "AWS::Cognito::UserPool"
Properties:
UserPoolName: "DemoApp"
Policies:
PasswordPolicy:
MinimumLength: 8
RequireUppercase: true
RequireLowercase: true
RequireNumbers: true
RequireSymbols: true
TemporaryPasswordValidityDays: 7
LambdaConfig: {}
Schema:
-
Name: "sub"
(...)
AutoVerifiedAttributes:
- "email"
MfaConfiguration: "OFF"
EmailConfiguration:
EmailSendingAccount: "COGNITO_DEFAULT"
AdminCreateUserConfig:
AllowAdminCreateUserOnly: false
UserPoolTags: {}
AccountRecoverySetting:
RecoveryMechanisms:
-
Priority: 1
Name: "verified_email"
-
Priority: 2
Name: "verified_phone_number"
UsernameConfiguration:
CaseSensitive: false
VerificationMessageTemplate:
DefaultEmailOption: "CONFIRM_WITH_CODE"
LADCognitoUserPoolClient:
Type: "AWS::Cognito::UserPoolClient"
Properties:
UserPoolId: !Ref LADCognitoUserPool
ClientName: "DemoAppClient"
RefreshTokenValidity: 30
ReadAttributes:
- "address" (...)
ExplicitAuthFlows:
- "ALLOW_CUSTOM_AUTH"
- "ALLOW_REFRESH_TOKEN_AUTH"
- "ALLOW_USER_SRP_AUTH"
- "ALLOW_USER_PASSWORD_AUTH"
GenerateSecret: false
PreventUserExistenceErrors: "ENABLED"
SupportedIdentityProviders:
- "COGNITO"
CallbackURLs:
- "https://example.com/callback"
LogoutURLs:
- "https://example.com/signout"
AllowedOAuthFlows:
- "code"
- "implicit"
AllowedOAuthScopes:
- "aws.cognito.signin.user.admin"
- "email"
- "openid"
- "phone"
- "profile"
AllowedOAuthFlowsUserPoolClient: true
IdTokenValidity: (...)
LADCognitoUserPoolDomain:
Type: "AWS::Cognito::UserPoolDomain"
Properties:
Domain: !Sub "ladauthdemo${LADApiGatewayStage}"
UserPoolId: !Ref LADCognitoUserPool
In the case of access_token from the hosted html I get tokens which decode like this:
{
"kid": "jFXB1AW4cAjbF1Ti+Tru/8ToxbYCAB1IYdCwEGfM7Sk=",
"alg": "RS256"
}
{
"sub": "eabb63bf-7bf2-464f-9d94-808239738981",
"iss": "https://cognito-idp.eu-west-2.amazonaws.com/eu-west-2_EyTaD2AsF",
"version": 2,
"client_id": "40vstm6vrhas7kokoasnrhf1g1",
"event_id": "2cbcc84d-db84-4ab5-91db-4088cd18829a",
"token_use": "access",
"scope": "aws.cognito.signin.user.admin phone openid profile email",
"auth_time": 1652623673,
"exp": 1652627273,
"iat": 1652623673,
"jti": "84921f7a-bd8e-4fea-a89d-984b1645b6e2",
"username": "testuser-adbed764-81f3-4b4f-950a-db2f041ff833#tunasoniq.io"
}
In the case of access_token from the api only the header part is base64 decodable. The header reads the same as the header above.
Here's an example of a whole encoded token from the api:
eyJraWQiOiJqRlhCMUFXNGNBamJGMVRpK1RydVwvOFRveGJZQ0FCMUlZZEN3RUdmTTdTaz0iLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiI1ZTJjYTRlOC0wYzNlLTQwZGYtYWJkMS0wOGFmOGI3MmM0YTIiLCJpc3MiOiJodHRwczpcL1wvY29nbml0by1pZHAuZXUtd2VzdC0yLmFtYXpvbmF3cy5jb21cL2V1LXdlc3QtMl9FeVRhRDJBc0YiLCJjbGllbnRfaWQiOiI0MHZzdG02dnJoYXM3a29rb2FzbnJoZjFnMSIsIm9yaWdpbl9qdGkiOiI4NDAyMjc4ZC0xMzA4LTRmZTgtODUyNC1kNjcxNjliNTg0ZjciLCJldmVudF9pZCI6IjdjMDA2YTQ1LWZiOTUtNDU1Mi05MWNhLTk2YTUwZjQ5MjRmOSIsInRva2VuX3VzZSI6ImFjY2VzcyIsInNjb3BlIjoiYXdzLmNvZ25pdG8uc2lnbmluLnVzZXIuYWRtaW4iLCJhdXRoX3RpbWUiOjE2NTI2MzgwMDgsImV4cCI6MTY1MjY0MTYwOCwiaWF0IjoxNjUyNjM4MDA4LCJqdGkiOiIwNjNlMzRhOC0wYzQ5LTRlNzItYTBjYi0wYjRhZTcxMTdhYmIiLCJ1c2VybmFtZSI6InRlc3R1c2VyLTM2MDkzZGZiLTViNWMtNGFkNi1hMzk4LWIwODQ2ODdlODUzN0B0dW5hc29uaXEuaW8ifQ.VQTz1cFOKUfn-QbOWfl4lk7gzbmptKS-tiCWlBAFbagMt4NvhAZVaLg_Sh7N8BNKw1apkjgQVeC0Coq4otQcDp04z4dt4kyvjFGUO7hdHbgDpp6vNL5pk3yy48a8Zw011gz9fZaAE9CcebBjLFWsWNix8JrN86CwtRiXcoBq6aRKeJez_HVPdLoT8cebESnmI5KbR7DeLcv_J7S-t5DJ9V__X9ksF8yybKlgiytBH7F9RvmCyMH3-8vuxLxOd8ZJ7MOx8NgXKjTw5ETkAWWWGSwp8FBw8Npkf2KmHiRMPaYjg-_sAJPirsc4tyBG8fDhwNCjnEFrj6VfzBjHxgadSA
Your API token is missing the email scope, which is required to access the APIG resource. You need to request the scopes which you want the token to be issued for when you make the call to the API, or you can manually add them with a Cognito pre-token-generation lambda.
There is no way that I've been able to discover, of getting back scopes other than 'aws.cognito.signin.user.admin' using the published APIs. The only way to get those scopes is to leverage the opaque magic in the hosted HTML from Cognito.
However you can add 'aws.cognito.signin.user.admin' to the AuthorizationScopes on your AWS::ApiGateway::Method and it works.
I think this a bit of an abuse of OAUTH process so don't use it to handle access to the nuclear codes.

Serverless Cognito - Sign Up with Username, Password, and Email

I am having trouble implementing Cognito via Serverless. The AWS Cognito documentation is poor. What I want to do is:
Allow user to sign up with a unique username (coolprogrammer69), a unique email, and password
After sign up, allow user to modify username (coolprogrammer69 -> awesomeprogrammer69) and email as long as it is unique
This is a typical auth flow for a forum website like Stackoverflow; however, I cannot find the right combination of attributes to make this a reality. Can you please help me figure out what I am doing wrong?
service: my-cool-infrastructure
package:
individually: true
provider:
name: aws
runtime: nodejs14.x
environment:
user_pool_id: { Ref: UserPool }
client_id: { Ref: UserClient }
iamRoleStatements:
- Effect: Allow
Action:
- cognito-idp:AdminInitiateAuth
- cognito-idp:AdminCreateUser
- cognito-idp:AdminSetUserPassword
Resource: "*"
functions:
login:
handler: user/login.handler
events:
- http:
path: user/login
method: post
cors: true
register:
handler: user/register.handler
events:
- http:
path: user/register
method: post
cors: true
privateAPI:
handler: user/private.handler
events:
- http:
path: user/private
method: post
cors: true
authorizer:
name: PrivateAuthorizer
type: COGNITO_USER_POOLS
arn:
Fn::GetAtt:
- UserPool
- Arn
claims:
- email
resources:
Resources:
UserPool:
Type: AWS::Cognito::UserPool
Properties:
UserPoolName: my-cognito-pool
Schema:
-
Name: email
Mutable: true
Required: true
-
Name: preferred_username
Mutable: true
Required: true
Policies:
PasswordPolicy:
MinimumLength: 8
RequireNumbers: true
RequireSymbols: true
UserClient:
Type: AWS::Cognito::UserPoolClient
Properties:
ClientName: user-pool-ui
GenerateSecret: false
UserPoolId: { Ref: UserPool }
AccessTokenValidity: 5
IdTokenValidity: 5
ExplicitAuthFlows:
- "ADMIN_NO_SRP_AUTH"

Override default auth in SAM templates and Open API

I have made a SAM template that deploys a mix of public and authenticated endpoints. The default auth is oauth. For public endpoints, I use overrides to make it auth NONE. This worked fine.
After I have added the OpenAPI for documentation. The auth override for public endpoints does not work anymore. What else should I do?
#sam-template.yaml
Resources:
RestApi:
Type: AWS::Serverless::Api
Properties:
Name: !Ref ApiStackName
StageName: Prod
Auth:
AddDefaultAuthorizerToCorsPreflight: false
DefaultAuthorizer: TokenAuthorizer
Authorizers:
TokenAuthorizer:
FunctionArn: !GetAtt Authorizer.Arn
Identity:
Header: Authorization
ValidationExpression: Bearer.*
ReauthorizeEvery: 0
DefinitionBody: // this is what I added.
Fn::Transform:
Name: AWS::Include
Parameters:
Location:
Fn::Join:
- ''
- - 's3://'
- Ref: S3BucketName
- '/swagger.yaml'
GetFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: ./functions
FunctionName: !Sub ${Environment}-api-get
Description: get
Handler: ./src/get.handler
Role: !Sub arn:aws:iam::${AWS::AccountId}:role/pam-${Environment}-${AWS::Region}-get-lambda-role
Events:
Api:
Type: Api
Properties:
RestApiId: !Ref RestApi
Path: /p
Method: GET
Auth:
Authorizer: NONE // this overrides the default auth
#swagger.yaml
/p:
get:
summary: Get
description: Get
responses:
200:
description: "200 response"
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/P"
500:
description: "500 response"
content: {}
x-amazon-apigateway-auth:
type: "NONE"
x-amazon-apigateway-integration:
uri:
Fn::Sub: arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${GetFunction.Arn}/invocations
responses:
default:
statusCode: "200"
passthroughBehavior: "when_no_match"
httpMethod: "POST"
contentHandling: "CONVERT_TO_TEXT"
type: "aws_proxy"
security: {}
OR
security:
- {}
Didn't work for me.
The below mentioned code snippet worked.
security:
- NONE: []
you are looking for security in OpenAPI. This sets Authorization on ApiGateway endpoint to NONE.
/p:
get:
summary: Get
description: Get
responses:
...
security:
- {}
x-amazon-apigateway-integration:
...

Cognito Userpool as identy provider with client credentials works only after saving in aws console

I'm deploying a serverless application in aws with the serverless framework. I was setting up AUTH2 with a cognito userpool and client credential authentication.
After the deployment it's not working, I get the error invalid grant, when I request a new token via postman.
When i login into the aws console, open the cognito app client page (refresh the page) and click the "save" button (without changing anything), it works. I can request as access token and can login into my app.
It works until my next deployment, so automatic deployment is not possible. What could be the reason? What happens when I click the save button?
This is my deployment code
resources:
Resources:
UserPoolDomain:
Type: AWS::Cognito::UserPoolDomain
Properties:
UserPoolId:
Ref: CognitoUserPool
Domain: "myapp-user-pool-domain"
CognitoUserPool:
Type: "AWS::Cognito::UserPool"
Properties:
MfaConfiguration: OFF
UserPoolName: myapp-user-pool
AdminCreateUserConfig:
AllowAdminCreateUserOnly: true
UsernameAttributes:
- email
CognitoUserPoolClient:
Type: "AWS::Cognito::UserPoolClient"
Properties:
ClientName: myapp-user-pool-client
GenerateSecret: True
UserPoolId:
Ref: CognitoUserPool
AllowedOAuthFlows: [ "client_credentials"]
ExplicitAuthFlows: ["ALLOW_USER_PASSWORD_AUTH","ALLOW_REFRESH_TOKEN_AUTH" ]
SupportedIdentityProviders: [ "COGNITO" ]
AllowedOAuthScopes: [ "myapp/odata4","myapp/trigger" ]
PreventUserExistenceErrors: ENABLED
ApiGatewayAuthorizer:
DependsOn:
- ApiGatewayRestApi
Type: AWS::ApiGateway::Authorizer
Properties:
Name: cognito-authorizer
IdentitySource: method.request.header.Authorization
RestApiId:
Ref: ApiGatewayRestApi
Type: COGNITO_USER_POOLS
ProviderARNs:
- Fn::GetAtt: [ CognitoUserPool, Arn ]
UserPoolResourceServer:
Type: AWS::Cognito::UserPoolResourceServer
Properties:
UserPoolId:
Ref: CognitoUserPool
Identifier: "myapp"
Name: "myapp"
Scopes:
- ScopeName: "results"
ScopeDescription: "provides myapp results"
- ScopeName: "trigger"
ScopeDescription: "trigger for myapp start"
Who can help?
Thanks
The following line has to be added to CognitoUserPoolClient: AllowedOAuthFlowsUserPoolClient: True. Then it works.