We are going to build a secure API gateway using AWS Cognito with OAuth 2.0 client credentials grant. AWS CDK with Python is employed to build a CloudFormation stack. This application architecture is composed of Amazon Cognito, Amazon API Gateway, and AWS Lambda.
- Python
- pipenv
- AWS CLI
- AWS CDK
- jq (if necessary)
-
Clone this repository
$ git clone [email protected]:yken2257/apigateway-client-credentials-cdk.git $ cd apigateway-client-credentials-cdk $ cp .env.example .env
-
Edit
.env
as you likeSTACK_NAME=SecuredApiGatewayStack COGNITO_DOMAIN_PREFIX=sendgrid-event-webhook-test
-
Install packages with pipenv
$ pipenv install $ pipenv shell
-
Load
.env
as bash variables$ export $(cat .env)
-
Deploy stack
$ cdk deploy
-
Get User Pool ID and API endpoint URL If the stack is successfully deployed, the User Pool ID and the endpoint URL of API Gateway are displayed.
Outputs: SecuredApiGatewayStack.CognitoUserPoolId = ap-northeast-1_xxxxxx SecuredApiGatewayStack.PrintPostStackApiEndpoint56282721 = https://yyyyyy.execute-api.ap-northeast-1.amazonaws.com/prod/
Define them as bash variables:
$ USER_POOL_ID=ap-northeast-1_xxxxxx $ ENDPOINT_URL=https://yyyyyy.execute-api.ap-northeast-1.amazonaws.com/prod/activity
Note that the path
activity
has to be added to the endpoint url.The User Pool ID and the URL can also be obtained with
aws cloudformation decribe-stacks
command:$ USER_POOL_ID=$(aws cloudformation describe-stacks \ --stack-name $STACK_NAME \ | jq -r '.Stacks[] | .Outputs[] | select(.OutputKey == "CognitoUserPoolId") | .OutputValue') $ ENDPOINT_URL=$(aws cloudformation describe-stacks \ --stack-name $STACK_NAME \ | jq -r '.Stacks[] | .Outputs[] | select(.ExportName == "PrintPostStackApi") | .OutputValue')'activity'
-
Create an app client and get its Client ID and Client Secret
$ aws cognito-idp create-user-pool-client \ --user-pool-id $USER_POOL_ID \ --client-name "full-access-client" \ --generate-secret \ --allowed-o-auth-flows "client_credentials" \ --allowed-o-auth-scopes "activity/*" \ --allowed-o-auth-flows-user-pool-client { "UserPoolClient": { "UserPoolId": "ap-northeast-1_xxxxxxxxx", "ClientName": "full-access-client", "ClientId": "<<CLIENT_ID>>", "ClientSecret": "<<CLIENT_SECRET>>", "LastModifiedDate": "2021-06-30T08:48:07.793000+09:00", "CreationDate": "2021-06-30T08:48:07.793000+09:00", "RefreshTokenValidity": 30, "AllowedOAuthFlows": [ "client_credentials" ], "AllowedOAuthScopes": [ "activity/*" ], "AllowedOAuthFlowsUserPoolClient": true } }
Memorize
<<CLIENT_ID>>
and<<CLIENT_SECRET>>
.$ CLIENT_ID=<<CLIENT_ID>> $ CLIENT_SECRET=<<CLIENT_SECRET>>
-
Request a token
$ TOKEN_URL=https://${COGNITO_DOMAIN_PREFIX}.auth.<<AWS_REGION>>.amazoncognito.com/oauth2/token $ AUTH=$(echo -n "${CLIENT_ID}:${CLIENT_SECRET}" | base64) $ curl --url $TOKEN_URL \ --header "Authorization: Basic $AUTH" \ --header "Content-Type: application/x-www-form-urlencoded" \ --data-urlencode "grant_type=client_credentials" {"access_token":"<<ACCESS_TOKEN>>","expires_in":3600,"token_type":"Bearer"}
For the token endpoint, see the official document.
-
Access the API endpoint
$ curl --url $ENDPOINT_URL \ --header "Authorization: Bearer <<ACCESS_TOKEN>>" \ --data '{"message":"Hello"}' {\"message\":\"Hello\"}
The Lambda function just responses the request body, if successfully configured. The request body would also be shown on the Amazon CloudWatch Logs.
SendGrid provides "Event Webhook", which POSTs the sending logs to your server. This has an option that enables you to secure your server with OAuth 2.0 Client Credentials grant. Test the configration of our API gateway with this feature. SendGrid POSTs JSON data when the token endpoint, client id, client secret, and the API endpoint are correctly configured. This can be tested via SendGrid's API:
$ curl --url https://api.sendgrid.com/v3/user/webhooks/event/test \
--header "Content-Type: application/json" \
--header "Authorization: Bearer $SENDGRID_API_KEY" \
--data '{"url": "'$ENDPOINT_URL'",
"oauth_client_id": "'$CLIENT_ID'",
"oauth_client_secret": "'$CLIENT_SECRET'",
"oauth_token_url": "'$TOKEN_URL'"}'
When the API is successfully called, check if the JSON data are POSTed on Amazon CloudWatch Logs. See the official docment for the SendGrid API.