Oauth 2 for API Gateway using AWS Cognito

When building a 3rd party API, OAuth 2 offers secure, short-lived tokens and granular access. This guide shows how to set up OAuth 2 with AWS Cognito for API Gateway via AWS CDK, removing the need for a custom auth Lambda.

3 days ago   •   3 min read

By Denys Burdeniuk
Photo by Rayner Simpson / Unsplash

Sometimes there is a need to create the API for 3rd party clients to integrate with your product. And then the question appears: what auth method to use. Oauth 2 is the most widely adopted standard for API auth. Oauth 2 is secure, tokens are short-lived and revocable, granular access is possible. In this blogpost we will show you how to create Oauth 2 authorization with AWS Cognito for your API Gateway using AWS CDK. Using Cognito this way eliminate a need for additional auth Lambda with custom code.

Prerequisites

  • You are familiar with AWS API gateway
  • You are familiar with infrastructure as code
  • You are familiar with AWS CDK and how to use it

Solution details

  • Use AWS Cognito Userpool as an authorizer
  • Create the machine-to-machine app client resource, attach needed scopes. When it’s created, you get client ID and secret credentials that you will have to share with 3rd party client
  • Create the domain for Cognito Userpool, that would look like a separate endpoint with different domain that your API gateway domain. This domain can be a generated one by AWS and look like: {YOUR_CUSTOM_PREFIX}.auth.{AWS_REGION}.amazoncognito.com or a custom one (in this case domain certificate in North Virginia region is required)
  • Create Cognito authorizer in your API Gateway and set Cognito Userpool as a source for it

How client application will interact with the API

Notes

  • In this example we create only one default custom scope. Of course you can create multiple custom scopes and attach them to different app clients and API Gateway endpoints according to your needs
  • We create only one proxy integration endpoint for API gateway for simplicity of the example

Out of scope of this example

  • API Gateway setup (other than auth related minimum for this example)
  • Deployment of your CDK defined infrastructure

AWS CDK code to create Oauth 2 with API Gateway infrastructure

// Cognito user pool
const userPool = new UserPool(this, 'UserPool')

// Custom resource server and scope
const customScope = new ResourceServerScope({
  scopeName: 'default',
  scopeDescription: 'Custom default scope'
})
const customResourceServer = userPool.addResourceServer('ResourceServer', {
  identifier: 'default',
  userPoolResourceServerName: 'Custom default resource server',
  scopes: [customScope]
})

// Cognito app clients
userPool.addClient('ThirdPartyAppClient', {
  userPoolClientName: 'ThirdPartyClientName',
  generateSecret: true,
  authFlows: {
    userSrp: false,
    userPassword: false,
    adminUserPassword: false,
    user: false,
    custom: false
  },
  oAuth: {
    flows: {
      clientCredentials: true
    },
    scopes: [OAuthScope.resourceServer(customResourceServer, customScope)]
  },
  refreshTokenValidity: Duration.days(5)
})

// Create cognito generated domain
userPool.addDomain('UserPoolDomain', {
  cognitoDomain: {
    domainPrefix: 'YOUR_CUSTOM_PREFIX' // Replace it with your domain prefix
  }
})

// Rest Api
const restApi = new RestApi(this, 'NewRestApi', {
  endpointTypes: [EndpointType.REGIONAL]
})

// Cognito authorizer
const authorizer = new CognitoUserPoolsAuthorizer(this, 'Authorizer', {
  cognitoUserPools: [userPool]
})

// Api proxy integration
const proxyLambda = new lambda.Function(this, 'ProxyLambda', {
  runtime: lambda.Runtime.NODEJS_22_X,
  handler: 'index.handler',
  code: lambda.Code.fromAsset('app/dist/src'),
  timeout: Duration.seconds(10)
})
const proxyIntegration = new LambdaIntegration(proxyLambda, {
  proxy: true,
  allowTestInvoke: true
})
const proxyResource = restApi.root.addResource('{proxy+}')
proxyResource.addMethod('ANY', proxyIntegration, {
  authorizer,
  authorizationScopes: ['default/default']
})

(Optional) AWS Code to create custom Cognito domain

// Create custom cognito domain
const hostedZoneId = 'YOUR_HOSTED_ZONE_ID' // Replace it with your hosted zone ID
const hostedZoneName = 'YOUR_HOSTED_ZONE_NAME' // Replace it with your hosted zone name

const hostedZone = HostedZone.fromHostedZoneAttributes(this, 'HostedZone', {
  hostedZoneId: hostedZoneId,
  zoneName: hostedZoneName
})
const cognitoCustomDomainName = `YOUR_SUBDOMAIN.${hostedZoneName}` // Replace it with your desired subdomain if needed

const cognitoCertificate = Certificate.fromCertificateArn(
  this,
  'CognitoCertificate',
  'YOUR_CERTIFICATE_ARN' // Replace with your certificate ARN, certificate must be in the us-east-1 (North Virginia) region
)

const customDomain = userPool.addDomain('UserPoolCustomDomain', {
  customDomain: {
    domainName: cognitoCustomDomainName,
    certificate: cognitoCertificate
  }
})

new ARecord(this, 'UserPoolCustomDomainAliasRecord', {
  zone: hostedZone,
  recordName: cognitoCustomDomainName,
  target: RecordTarget.fromAlias(new UserPoolDomainTarget(customDomain))
})

Testing of auth endpoint after deployment

You can simply test it with CURL command:

curl -X POST {YOUR_COGNITO_DOMAIN}/oauth2/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=client_credentials&client_id=CLIENT_ID&client_secret=CLIENT_SECRET"

Alternatively use tools like Postman.

Conclusion

That’s it! ;) You have just seen how easy Oath 2 auth for API Gateway can be created.

Spread the word

Keep reading