Notes On AWS Cognito

Concepts

OAuth

OAuth is a protocol to allow user via an authentication provider to give another website/service a limited access authentication token for authorization to additional resources. It is primarily authorization framework, not authentication.

Authentication deals information about "who one is". Authorization deals information about "who grants what permissions to whom". Authorization flow contains authentication as its first step.

There are many libraries and services that use OAuth 2.0 for authentication. It is often called "social login". If you see "OAuth authentication" (not "OAuth authorization"), it is a solution using OAuth for authentication.

OAuth says absolutely nothing about the user, nor does it say how the user proved their presence or even if they're still there. As far as an OAuth client is concerned, it asked for a token, got a token, and eventually used that token to access some API. The token is opaque.

You can build an authentication and identity protocol on top of OAuth delegation and authorization protocol. And in fact, there are a number of well-known recipes out there for doing this with specific providers, like Facebook Connect, Sign In With Twitter, and OpenID Connect (which powers Google's sign-in system, among others). These recipes each add a number of items, such as a common profile API, to OAuth to create an authentication protocol.

General OAuth does not define a specific format or structure for the access token itself, protocols like OpenID Connect's ID Token and Facebook Connect's Signed Response provide a secondary token along side the access token that communicates the authentication information directly to the client. This allows the primary access token to remain opaque to the client, just like in regular OAuth.

See Also:

OAuth 2.0

OAuth 2.0 is a framework where a user of a service can allow a third-party application to access his/her data hosted in the service without revealing his/her credentials (ID & password) to the application.

OAuth2.0 provides following compared to OAuth 1:

  • More OAuth Flows to allow better support for non-browser based applications.
  • Delegates security to https, so does not require complicated cryptography signs
  • Introduces the notion of refresh tokens. Enables short lived access tokens.
  • OAuth 2.0 is a flexible authorization framework, not a protocol.

  • The authorization endpoint is used to interact with the resource owner and obtain an authorization grant. The authorization server MUST first verify the identity of the resource owner. The way in which the authorization server authenticates the resource owner (e.g., username and password login, session cookies) is beyond the scope of this specification.

  • Client needs access token which can be used to access an API endpoint.

  • It describes 5 methods(grants) for client application to acquire access token:

    Authorization code grant: 
      - client_id, client_secret, scope, code, redirect_url, 
        access-token, refresh-token
      - grant_type is "authorization code"
    
    Implicit grant
      - For Single Page App client. 
      - Similar to above but Single step get access-token.
      - No client_secret, No refresh token
    
    Resource owner credentials grant
      - client_id, client_secret, scope, 
        username, password, access-token, refresh-token
      - No redirect_uri
      - grant_type is "password"
    
    Client credentials grant
      - Simplest and for machine to machine trusted authentication
      - client_id, client_secret, scope, access-token
      - No refresh token, redirect_uri
      - grant_type is "client_credentials"
    
    Refresh token grant
      - submit refresh token and get new access token
      - grant_type is "refresh token"
    

OAuth 2.0 Scope

OAuth scope is a list of permissions. e.g. "READ WRITE" OpenID connect uses scope values: profile, email, address, and phone, etc (which means access to email or phone information etc)

OpenID Connect

OAuth defines no specific token format, defines no common set of scopes for the access token, and does not at all address how a protected resource validates an access token. OpenID-Connect fills that gap.

OpenID Connect also uses the JSON Object Signing And Encryption (JOSE) suite of specifications.

OpenID Connect defines an important component called ID Token:

  • The OpenID Connect ID Token is a signed JSON Web Token (JWT) that is given to the client application along side the regular OAuth access token.
  • The ID Token contains a set of claims about the authentication session, including:
    • an identifier for the user (sub),
    • the identifier for the identity provider who issued the token (iss), and
    • the identifier of the client for which this token was created (aud).
  • Additionally, the ID Token contains information about the validity period.
  • Since the format of the ID Token is known by the client, it is able to parse.
  • It is issued in addition to (and not in lieu of) an access token, allowing the access token to remain opaque to the client as defined in regular OAuth.
  • Finally, the token itself is signed by the identity provider's private key, adding an additional layer of protection to the claims inside of it.

OAuth 2 vs OpenID Connect

In OpenID, authentication is delegated: server A wants to authenticate user U, but U's credentials (e.g. U's name and password) are sent to another server, B, that A trusts (at least, trusts for authenticating users). Indeed, server B makes sure that U is indeed U, and then tells to A: "ok, that's the genuine U".

In OAuth, authorization is delegated: entity A obtains from entity B an "access right" which A can show to server S to be granted access; B can thus deliver temporary, specific access keys to A without giving them too much power. You can imagine an OAuth server as the key master in a big hotel; he gives to employees keys which open the doors of the rooms that they are supposed to enter, but each key is limited (it does not give access to all rooms); furthermore, the keys self-destruct after a few hours.

To some extent, authorization can be abused into some pseudo-authentication, on the basis that if entity A obtains from B an access key through OAuth, and shows it to server S, then server S may infer that B authenticated A before granting the access key. So some people use OAuth where they should be using OpenID.

User Pool

  • User pool is a collection of Users (End Users)
  • In addition, A User Pool can be associated with multiple app clients.

Identity Pool

App Client

  • A User Pool can have multiple app clients.
  • App client usually have client_secret, but this can be disabled. An app client without client_secret is still useful since this app has capabilities to connect to cognito to serve login UI and then get access token. (OAuth Implicit Grant Type method). The login/password validation itself may use SRP protocol.
  • If your app is a server app, select Enable sign-in API for server-based authentication (ADMIN_NO_SRP_AUTH) in user pool configuration.
  • By default, user pools allow your app to read and write all attributes. You can restrict your App to not being able to write to some optional attributes of the user. (App will always be able to write required attributes)
  • The app client also has a list of associated scopes (permissions) that it may allow requests for. These scopes are declared by the Resource Server(s) in the User Pool.

SAML

  • Security Assertion Markup Language 2.0 (SAML) is an open standard for exchanging identity and security information with applications and service providers.
  • It is OpenID Connect alernative and an older protocol. You can not use it for Mobile applications.
  • Applications and service providers that support SAML enable you to sign in using your corporate directory credentials, such as your user name and password from Microsoft Active Directory. With SAML, you can use single sign-on (SSO) to sign in to all of your SAML-enabled applications by using a single set of credentials.
  • You can enable SAML authentication for your AWS accounts by using AWS Identity and Access Management (IAM). The trusted entity of IAM Role can be configured as SAML compliant external user.
  • In SAML, the user is redirected from the Service Provider (SP) to the Identity Provider (IDP) for sign in.
  • In OpenID Connect, the user is redirected from the Relying Party (RP) to the OpenID Provider (OP) for sign in.
  • The SAML SP is always a website. The OpenID Connect RP is either a web or mobile application, and is frequently called the “client” because it extends an OAuth 2.0 client.

Federation

Cognito Domain

  • A Domain is tied to a user pool in a 1:1 relationship,
  • It is used to host the signup/login/challenge pages for the auth
  • It is also used to host /oauth2/token endpoint.

SRP

Secure Remote Password Protocol. See http://srp.stanford.edu/ This protocol resists both passive and active network attacks. Involves a strong derived session key as result of sequence of exchanges of short messages as per protocol.

Signature Version 4 Signing Process

Add authentication information in HTTP header or query string itself using a signing key which is derived from shared key plus timestamp plus payload. Most aws cli commands use this style.

If you have middleware server which interacts with AWS cognito, you would probably use this as well.

JWT - Jason Web Token

Cognito access tokens are JWT, which are signed with JWK (Jason Web Key). The JWT contains standard claims, but can also be extended to contain custom claims. The reserved standard claims are:

iss (issuer): Issuer of the JWT
sub (subject): Subject of the JWT (the user)
aud (audience): Recipient for which the JWT is intended
exp (expiration time): Time after which the JWT expires
nbf (not before time): Time before which JWT must not be accepted.
iat (issued at time): Time at which the JWT was issued;
jti (JWT ID): Unique identifier; Allows a token to be used only once.

Resource Server

  • Resource server is a place holder for scopes (permissions)

  • There can be multiple resource servers associated with a single User Pool.

  • By the time the request makes it to the Resource Server, it has an access token which reveals user and the session.

  • An example Resource Server called transactions in which we define 2 scopes called get and post transactions :

    aws> cognito-idp create-resource-server --name transactions --identifier
    transactions --user-pool-id us-east-1_0Pe***** --scopes
    ScopeName=get,ScopeDescription=get_tx ScopeName=post,ScopeDescription=post_tx
    {
        "ResourceServer": {
            "UserPoolId": "us-east-1_0Pe*****",
            "Identifier": "transactions",
            "Name": "transactions",
            "Scopes": [
                {
                    "ScopeName": "post",
                    "ScopeDescription": "post_tx"
                },
                {
                    "ScopeName": "get",
                    "ScopeDescription": "get_tx"
                }
            ]
        }
    }
    

Client Side Authentication Flow

Mobile or Web client authentication Involves 2 steps :

  • Initiate Auth(SRP) means authenticate using secure remote password protocol.
  • RespondToAuthChallenge call with response (e.g. MFA MultiFactor Auth)

Server Side Authentication Flow

Instead of client side app, if you have backend server side App, the authentication flow is :

  • Initiate AdminInitiateAuth API (instead of InitiateAuth). This requires AWS admin credentials.

  • AdminRespondToAuthChallenge API (instead of RespondToAuthChallenge), which also requires AWS admin credentials.

  • For server side App it is not mandtory to use AdminInitiateAuth. However, it is recommended.

  • The request looks like :

    AdminInitiateAuth Request {
        "AuthFlow":"ADMIN_NO_SRP_AUTH",
        "AuthParameters":{
            "USERNAME":"<username>",
            "PASSWORD":"<password>"
            },
        "ClientId":"<clientId>",
        "UserPoolId":"<userPoolId>"
    }
    

S3 bucket, object, meta data

S3 is a simple key value store. Each object supports :

Key        – The file path
Version ID – You can have different versions of same object!
Value      – The content that you are storing.
Metadata   – A set of name-value pairs. Both system and your own.
Subresources – Example: ACL

IAM Policy vs Bucket Policy vs ACL

    1. Access Control Lists (ACLs) are legacy (but not deprecated),
    1. bucket/IAM policies are recommended by AWS, and
  • 3) ACLs give control over buckets AND objects, policies are only at the bucket level.
    1. IAM policy is useful to know what IAM users can do.

Both S3 bucket and IAM Policy follows standard AWS policy syntax. S3 bucket policy contains "Principal", but IAM policy does not. Both contains "Resource" pointing to bucket/objects. A policy is represented as "Statement".

Sample S3 Bucket policy:

{
"Version": "2012-10-17",
"Statement": [
  {
    "Effect": "Allow",
    /* Use "Principal" : "*"  for public access */
    "Principal": {
      "AWS": ["arn:aws:iam::111122223333:user/Alice",
              "arn:aws:iam::111122223333:root"]
    },
    "Action": "s3:*",
    "Resource": ["arn:aws:s3:::my_bucket",
                 "arn:aws:s3:::my_bucket/*"]
  }
]
}

Sample IAM policy:

{
  "Version": "2012-10-17",
  "Statement":[{
    "Effect": "Allow",
    "Action": "s3:*",
    "Resource": ["arn:aws:s3:::my_bucket",
                 "arn:aws:s3:::my_bucket/*"]
    }
  ]
}

Using ACL, you give permission using User UUID or GROUP, etc. ACL is a "subresource" attached to S3 bucket and objects. Default ACL is created to grant full-control only for the owner of the bucket/object.

Sample bucket ACL:

{
 "Owner": { "DisplayName": "thavamuni", "ID": "78a2..." },
 "Grants": [
     {
         "Grantee": {
             "Type": "CanonicalUser",

 // "Type": "CanonicalUser"|"AmazonCustomerByEmail"|"Group",

             "DisplayName": "thavamuni",
             "ID": "78a2170...b8a993"
         },
         "Permission": "FULL_CONTROL"
     },
     {
         "Grantee": {
             "Type": "Group",
             "URI": "http://acs.amazonaws.com/groups/global/AllUsers"
         },
         "Permission": "READ"
     }
   ]
 }

Enable CORS with Apache

<Directory /var/www/html>
     Order Allow,Deny
     Allow from all
     AllowOverride all
     Header set Access-Control-Allow-Origin "*"
</Directory>

# Add/activate module
a2enmod headers

Authentication Flow Diagram

GetId
GetCredentialsForIdentity

Using cli:

# Step 0. It is handled by User pool.
aws cognito-idp initiate-auth --client-id 1jtj0a0peedlgfdhml3dr5t8j 
    --auth-flow USER_SRP_AUTH --auth-parameters USERNAME=myuser,PASSWORD='xxx'

See  https://github.com/capless/warrant

from warrant import Cognito

u = Cognito('your-user-pool-id','your-client-id', username='bob')

u.authenticate(password='bobs-password')


# This is done by Identity pool. You submit pool-name and IdToken
# If you do not pass logins, you can "unauthenticated" IndentityId
aws cognito-identity get-id --identity-pool-id <value> --logins <value>


# Submit IdentityId and poolname, IdToken.
aws cognito-identity    get-credentials-for-identity  --identity-id <value>
    --logins  <value>     # STS Service

Get Current Execution Role for Lambda

  • How to get it ?
  • How does it look like ?

::
print("Current user: " + boto3.resource('iam').CurrentUser().arn)

User: arn:aws:sts::749340585813:assumed-role/golfnow-start-job/dev-StartJobLambda-HZO22Z5IMTFB

get_caller_identity() method in boto3. This is under sts service. It will return the ARN of the entity attached to the Lambda function

  • list_attached_user_policies lists all managed policies for IAM user.
  • If you want just the inline policies: get_user_policy

Request Context in API Gateway using Cognito Credentials

While using temporary credentials acquired through cognito user pool ... :

"requestContext": {
    "resourceId": "n1wvn3",
    "resourcePath": "/hello",
    "httpMethod": "GET",
    "extendedRequestId": "Y2rf7FeAIAMF0YQ=",
    "path": "/dev/hello",
    "accountId": "027212312845",
    "stage": "dev",
    "domainPrefix": "dbp1ufswd1",
    "requestId": "17993b5a-69c6-11e9-a87a-b306e5d12fd3",
    "identity": {
      "cognitoIdentityPoolId": "us-east-1:bde2164e-cd74-4ca6-a0dc-13adcc08f449",
      "accountId": "027212312845",
      "cognitoIdentityId": "us-east-1:63f46f9e-87df-4350-93ee-012705073a5e",
      "caller": "AROAQMVP26EG5FSTO67ZH:CognitoIdentityCredentials",
      "sourceIp": "162.216.143.81",
      "accessKey": "ASIAQMVP26EGZ3FQBPJH",
      "cognitoAuthenticationType": "authenticated",
      "cognitoAuthenticationProvider": "cognito-idp.us-east-1.amazonaws.com/
           us-east-1_KrWqAV47M,cognito-idp.us-east-1.amazonaws.com/
           us-east-1_KrWqAV47M:CognitoSignIn:
           5a9226ef-6805-4297-a00e-cc9716d8f82d",
      "userArn": "arn:aws:sts::027212312845:assumed-role/
                  hdemo-group-admin-iam/CognitoIdentityCredentials",
      "user": "AROAQMVP26EG5FSTO67ZH:CognitoIdentityCredentials"
    },
    "domainName": "dbp1ufswd1.execute-api.us-east-1.amazonaws.com",
    "apiId": "dbp1ufswd1"
  }

API Gateway Identity for Unatuthenticated Call

"identity": {
    "cognitoIdentityPoolId": "us-east-1:bde2164e-cd74-4ca6-a0dc-13adcc08f449",
    "accountId": "027212312845",
    "cognitoIdentityId": "us-east-1:5f17db2c-56da-49d3-b649-4ab822f423e5",
    "caller": "AROAQMVP26EG44Z3WPNY5:CognitoIdentityCredentials",
    "sourceIp": "162.216.143.81",
    "accessKey": "ASIAQMVP26EG5DRXOJEO",

    "cognitoAuthenticationType": "unauthenticated",

    "cognitoAuthenticationProvider": null,
    "userArn": "arn:aws:sts::027212312845:assumed-role/
                hdemo-20190419151145-unauthRole/CognitoIdentityCredentials",
    "user": "AROAQMVP26EG44Z3WPNY5:CognitoIdentityCredentials"
 }

Request Context in API Gateway using root IAM Credentials

"identity": {
      "cognitoIdentityPoolId": null,
      "accountId": "027212312845",
      "cognitoIdentityId": null,
      "caller": "027212312845",
      "sourceIp": "162.216.143.81",
      "accessKey": "AKIAIGODE5VFHYMUCXQQ",
      "cognitoAuthenticationType": null,
      "cognitoAuthenticationProvider": null,
      "userArn": "arn:aws:iam::027212312845:root",
      "userAgent": "insomnia/6.4.1",
      "user": "027212312845"
    },

Request Context in API Gateway using IAM Credentials

"identity": {
        "cognitoIdentityPoolId": null,
        "accountId": "027212312845",
        "cognitoIdentityId": null,
        "caller": "AIDAQMVP26EGTDG6WOJ5K",   // IAM User Id
        "sourceIp": "162.216.143.81",
        "accessKey": "AKIAQMVP26EGRMWMGQOD",
        "cognitoAuthenticationType": null,
        "cognitoAuthenticationProvider": null,
        "userArn": "arn:aws:iam::027212312845:user/hdemo-iam",
        "user": "AIDAQMVP26EGTDG6WOJ5K"
      }

Unauth Role not having permission

"Message": "User: arn:aws:sts::027212312845:assumed-role/
          hdemo-20190419151145-unauthRole/CognitoIdentityCredentials
          is not authorized to perform: execute-api:Invoke on resource:
          arn:aws:execute-api:us-east-1:********2845:dbp1ufswd1/dev/GET/hello"

Action Syntax For Invoking API :

execute-api:Invoke
execute-api:InvalidateCache

Resource Syntax:

"arn:aws:execute-api:us-east-1:account-id:api-id/*/GET/pets"

Action Syntax For Managing API end points::
apigateway:GET apigateway:*

Meaning of amr in AWS Cognito

  • amr stands for 'Authentication Methods References'.
  • That comes from the OpenID Connect specification.
  • It is either "authenticated" or "unauthenticated"
  • If it is authenticated, then provider name points to cognito or google, etc.

Permissions

Thumbnail Generation Application Example

Consider automatic invocation of Lambda on S3 upload to create thumbnail images into another S3 bucket.

There are various permissions involved:

  • The user who uploads to S3 bucket needs IAM permission to do so. ObjectCreated event is fired.
  • The S3 event notification configuration suggests to invoke Lambda, but it has to have permission for Lambda:execute. You must explicitly configure the permission to grant S3 service to invoke you lambda function.
  • Lambda function needs runtime permissions to do it's job which is described by it's IAM execution Role.

Lambda Resources

  • A Lambda function.
  • An access policy associated with your Lambda function that grants Amazon S3 permission to invoke the Lambda function.

Note: Who will be the owner of the bucket when Lambda creates S3 bucket ?

IAM Resources

  • An execution role that grants permissions that your Lambda function needs through the permissions policy associated with this role.

Amazon S3 Resources

A source bucket with a notification configuration that invokes the Lambda function.

A target bucket where the function saves resized images.

AWS Account Identifiers

s3.listBuckets(function(err,data) {  
   if (!err)  canonicalAccountId = data.Owner.ID;
}

AWS assigns two unique IDs to each AWS account:

  • An AWS account ID such as 123456789012
  • A canonical user ID which looks like 79a59df900b949e55d96a1e698fbacedfd6e09d98eacf8f8d5218e7cd47ef2be.

API Gateway

  • API Gateway creates stateless REST APIs and also stateful websocket APIs.
  • It provides access to Lambda functions, S3, EC2, DynamoDB, VPC, or some website on internet, etc.
  • Canary release deployments for safely rolling out changes.
  • CloudTrail logging and monitoring of API usage and API changes.
  • CloudWatch access logging and execution logging, including the ability to set alarms.
  • Ability to use AWS CloudFormation templates to enable API creation.
  • API Gateway V1 and V2 APIs – https://docs.aws.amazon.com/apigateway/api-reference/

IAM - Identity Access Management

Concepts

Users, Groups, Roles, Policies, Identity Providers

IAM Users

Example: my-iam-user1, my-ec-admin, my-s3-admin

Users inherit permissions from Group Policies and directly attached inline policies.

Example User ARN arn:aws:iam::571600550186:user/my-iam-user1

IAM Groups

Example: my-admins, my-testers

Groups can be associated with one or more policies.

IAM Policies

There are more than 500 total Built-in AWS managed policies exist.

Example policy is Administrator Access :

{
  "Version": "2012-10-17",
  "Statement": [
      {
          "Effect": "Allow",
          "Action": "*",
          "Resource": "*"
      }
  ]
}

IAM Roles

  • IAM roles issue keys that are valid for short durations.
  • Roles are also associated with "Set of Policies" much like Users.
  • An user can "assume" an IAM Role dynamically on need basis. Atmost only one IAM role can be assumed at a time.
  • An application or AWS Service (Lambda, EC2, etc) also can assume any one Role at a time. These are called "Trusted Entities" of the role that is associated with that role.
  • Trusted entities for a role can inclue an IAM user in another account
  • IAM roles allow you to delegate access with defined permissions to trusted entities without having to share long-term access keys.
  • Role is a way to provide permissions to someone (a customer, supplier, contractor, employee, an EC2 instance, some external application outside AWS trying to consume your services) without creating a user for it.
  • You assume an IAM role by calling the AWS Security Token Service (STS) AssumeRole APIs (in other words, AssumeRole, AssumeRoleWithWebIdentity, and AssumeRoleWithSAML). These APIs return a set of temporary security credentials that applications can then use to sign requests to AWS service APIs.

Temporary security credentials

  • Temporary security credentials consist of the AWS access key ID, secret access key, and security token.
  • The credentials are valid for a specified duration and for a specific set of permissions.
  • Temporary security credentials are sometimes simply referred to as tokens.
  • Tokens can be requested for IAM users or for federated users.
  • To request temporary security credentials for federated users, You can call the GetFederationToken, AssumeRole, AssumeRoleWithSAML, or AssumeRoleWithWebIdentity STS APIs.

FAQ

Add Social Sign-in to User pool

  • Register with a Social IdP i.e. Facebook or Google for developer account and get your APP Id and Secret.
  • e.g. while registering with Facebook, Specify your web-site as https://<your-user-pool-domain>/oauth2/idpresponse And specify App Domains as: https://<your-user-pool-domain>
  • Add a Social IdP to Your Amazon User Pool. e.g. Manage User Pools => Identity Providers => Facebook, enable scopes such as public_profile, email, etc.

Common Amazon Cognito Scenarios

  • From single page App (or Mobile App) authenticate with user pool, and get access token. This token is useful as mentioned below:
    • This token can be used to connet to API Gateway.
    • This token can be used to connet to AWS AppSync.
    • If you want to directly access AWS resources, you must exchange this token to retrieve temp AWS credentials using Identity Pool
  • Your Web App may do authentication on user's behalf and get all details about users including the user pool groups and custom attributes. It can use these information to restrict access to your own resources.
  • Single page App can acquire AWS credentials by using only Identity Pool (There is no User Pool at all). This is done by:
    • Directly get a Idp token from 3rd Party e.g. Facebook.
    • Exchange Idp Token with AWS Credentials using Identity Pool.

API End points

FORM_BASED_LOGIN_ENTRY_POINT = "/api/auth/login";
TOKEN_BASED_AUTH_ENTRY_POINT = "/api/**";
TOKEN_REFRESH_ENTRY_POINT    = "/api/auth/token";

AWS Support Tools

You have tools for Cognito, API Gateway, etc

How to Decode JWT Tokens

const jwt = require('jsonwebtoken');

decode: (token) => {
   return jwt.decode(token, {complete: true});
}

Loading lodash from console

import _ from "lodash";
window.lodash = _;
// ------------------------------------
fetch('https://cdn.jsdelivr.net/npm/lodash@4.17.4/lodash.min.js')
    .then(response => response.text())
    .then(text => eval(text))

Interpret Session Object

Session :: { idToken, accessToken, refreshToken }

"refreshToken": {
  "token": "eyJj..."
},

"accessToken": {
  "jwtToken": "eyJraWQ...",
  "payload": {
    "sub": "5a9226ef-6805-4297-a00e-cc9716d8f82d",
    "cognito:groups": [
      "uspool"
    ],
    "event_id": "6e6f0754-6467-11e9-9e9c-39924d903840",
    "token_use": "access",
    "scope": "aws.cognito.signin.user.admin",
    "auth_time": 1555873101,
    "iss": "https://cognito-idp.us-east-1.amazonaws.com/us-east-1_KrWqAV47M",
    "exp": 1555876703,
    "iat": 1555873103,

    // JWT ID : Unique ID for the JWT
    "jti": "1f69a0c7-0827-4923-9c5b-ac5591053f19",
    "client_id": "33kcb9cvhdlncombg68a6q68un",
    "username": "zelestica"
  }
},
"clockDrift": -5    // Clock difference between server and client.
}

Login Id/User Pool Id: 
   cognito-idp.us-east-1.amazonaws.com/us-east-1_KrWqAV47M 

JWT Id Token: {

  "header": {
    // kid is key id, Public Key used to sign.
    "kid": "vn4iEO9s4nJNzqRdriSrMS9MWa25sltC42Wy5PGdFLw=",
    "alg": "RS256"             // Algorithm used.
  },

  "payload": {

    // sub is Cognito User UniqueID.
    "sub": "5a9226ef-6805-4297-a00e-cc9716d8f82d",  

    // Associated Groups ...
    "cognito:groups": [
      "uspool"
    ],
    "email_verified": true,

    // Effective IAM Role ...
    "cognito:preferred_role": "arn:aws:iam::027212312845:role/hdemo-group-admin-iam",

    // Issuing User Pool ID
    "iss": "https://cognito-idp.us-east-1.amazonaws.com/us-east-1_KrWqAV47M",
    "phone_number_verified": true,

    "cognito:username": "zelestica",

    // All associated IAM Roles
    "cognito:roles": [
      "arn:aws:iam::027212312845:role/hdemo-group-admin-iam"
    ],

    // "aud" is audience. ie. App Client ID
    "aud": "33kcb9cvhdlncombg68a6q68un",

    "event_id": "6e6f0754-6467-11e9-9e9c-39924d903840",

    // Implies it is ID Token
    "token_use": "id",
    "auth_time": 1555873101,
    "phone_number": "+919731595054",

    // Expiry time. 3600 seconds later. i.e. 1 hour
    "exp": 1555876703,              // Expiry claim.
    "iat": 1555873103,              // Issued-At claim
    "email": "zelestica@gmail.com"
  },
  "signature": "pBTFc....UYA"
}

Synopsis

Libraries and Reference Implementations

AWS Amplify

Followin packages/libraries are now part of AWS amplify. Some of them used to exist separately before:

  • amazon-cognito-identity-js
  • aws-amplify-react
  • aws-amplify-react-native
  • aws-amplify-angular

Build serverless React App using aws amplify :

Github aws/amazon-cognito-identity-js

  • Updated 2 Years ago, based on babel-webpack, Now part of aws-amplify.
  • Uses SDK directly. Contains lot of Userpools functionality.
  • Does not have hosted login UI

Github aws/amazon-cognito-auth-js

  • Has support for login hosted UI

Github dabit3/aws-amplify-workshop-react

See https://github.com/dabit3/aws-amplify-workshop-react Excellent example to create react app with cognito.

  • Turns out you really have to run amplify config BEFORE you run amplify init. It sets up a special AWS iam user for itself to use.
  • Be careful about "amplify remove auth", it may wipe out all your production stuff as well.
  • amplify is great for react setup with AWS but sometimes you may want to create userpools yourself.

Github 0xdevalias/poc-aws-cognito

The README is very informative.

aws-serverless-express

Run express based framework for using with Lambda.

aws-serverless-ember

Highlights:

aws  cloudformation deploy --template-file hosting.yaml --stack-name
     ember-serverless-hosting --capabilities CAPABILITY_IAM

hosting.yaml:

   AWSTemplateFormatVersion: '2010-09-09'
   Description: Ember Serverless Hosting
   Resources:
     WebsiteBucket:
       Type: AWS::S3::Bucket
       Properties:
         AccessControl: PublicRead
         WebsiteConfiguration:
           IndexDocument: index.html
           ErrorDocument: index.html
   ...

aws cloudformation describe-stacks --stack-name ember-serverless-api
# Outputs bucket names and such.

# Now deploy API gateway REST API endpoints using .yaml file.
./deploy.sh --stack ember-serverless-api --template api.yaml --bucket
         <<bucket-name-from-above-output>>

Github awslabs/aws-serverless-auth-reference-app

Cognito auth, etc. Blue prints and examples. spacefinder conference room booking application.

Authentication Flow

JWT Authentication

Token based authentication schema's became immensely popular in recent times as they provide important benefits when compared to sessions/cookies:

CORS
No need for CSRF protection
Better integration with mobile
Reduced load on authorization server
No need for distributed session store

Some trade-offs have to be made with this approach:

More vulnerable to XSS attacks.
Access token can contain outdated authorization claims  --
(e.g when some of the user privileges are revoked).
Access tokens can grow in size in case of increased number of claims.
File download API can be tricky to implement.
True statelessness and revocation are mutually exclusive.

JWT Authentication flow is very simple:

  • User obtains Refresh and Access tokens by providing credentials to the Authorization server
  • User sends Access token with each request to access protected API resource Access token is signed and contains user identity (e.g. user id) and authorization claims.
  • Let's say that authorization claims (e.g user privileges in the database) are changed during the life time of Access token. Those changes will not become effective until new Access token is issued. This is not big issue usually, because Access tokens are short-lived.

Cognito Authentication Flow

  • Users in user pool do have username/password and they authenticate with cognito user pool using SRP (secure remote protocol). As a response to SRP, user gets identity-token, access-token, refresh-token as part of JWT token. This protocol is compliant to OpenId Connect Open Standard.

  • Identity JWT Token looks like:

    Header:    alg:RS256,  
               kid:xxx,     (Key id used to sign the token)
    payload:   email:..., 
               iss:cognito...,
               "cognito:username":...,
               given_name:Test, 
               token_use:id,     (Indicates this is ID token)
               aud:_client-id_   (audience claim)
               email_verified:true, ...
    Signature
    
  • Access JWT Token looks like :

    Header:  Key-Id and alg
    Payload:
      {
          "auth_time": 1500009400,
          "exp": 1500013000,
          "iat": 1500009400,
          "iss": "https://cognito-idp...azonaws.com/us-east-1_example",
          "scope": "aws.cognito.signin.user.admin",
          "sub": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee",
              // Subject is unique UUID for the user. Temp identity ???
              // Probably unique across userpools ???
          "token_use": "access",
          "username": "janedoe@example.com"
      }
    Signature
    
  • Refresh JWT Token looks like :

    Header { "alg": "HS512" }
    Claims
        {
          "sub": "svlada@gmail.com",
          "scopes": [ "ROLE_REFRESH_TOKEN" ],
          "iss": "http://svlada.com",
    
          // JWT ID(jti) claim used to uniquey identify Refresh token
          "jti": "90afe78c-1d2e-4869-a77e-1d754b60e0ce",
    
          "iat": 1472033308,
          "exp": 1472036908
        }
    Signature (base64 encoded)
    
  • There are 3rd party Identity provider alternatives which can replace cognito's role here. e.g. Auth0, Google, Facebook, etc. i.e. Submit your password using SRP and get some token.

  • Amazon Federated Identity Pool is used to exchange this token, for AWS IAM credentials. (This is called a bearer token). The protocol involves Get Identity ID request and gets the same. Then it submits Identity ID (as JWT) and requests Get Credentials,

  • If you are using Cognito, you can skip this step unless you want to login using google/facebook password. (Verify this)

  • Open question: How to map between UserPool and Federated Identity Pool (e.g. Google Login) ?

  • Signout operation revokes all tokens for the user: Identity, Access and Refresh tokens all are revoked. Authorization server is informed of this Signout operation, however Resource servers are not aware of this revocation, so until the life of access token it will remain valid??? Singout done by using the GlobalSignOut and AdminUserGlobalSignOut APIs.

Authentication Flow Diagram

----------------------------------------------------------------------
.
.
.  User| --- Username/passwd using SRP -------> | Authorization Server
.      | <---Gets Id, Access, Refresh Tokens--- | (e.g. Cognito)
.      |      Id Token: 1 hour valid            |               
.      |                                        |               
.      |                                        |               
.      | ----Submit Access Token --Req Access-> | Resource Server 
.      |     (Operation Done if Token is valid) | (Application Specific)
.      |                                        |               
.      | ---- Submit Refresh Token -----------> | Authorization Server
.      | <---Gets Id, Access, Refresh Tokens--- | (e.g. Cognito)
.      |                                        |               
.      | ---- Submit Identity Token ----------> | Federated Identity
.      | <--- Gets AWS Identity ID  ----------- | Server (AWS)
.      | ---- Submit AWS Identity ID ---------> | (Using STS Security
.      | <--- Gets Temp AWS Credentials ------  |   Token Service)
.      |      (Access Key ID, Secret Key)       |
.      |                                        | 
.      |                                        |               
.      | --- Get Access to AWS Resource ------> | AWS Resource  
.      |     (using temp AWS Credentials)       |               
.      |     (Request signed by Sig V4 algo)    |               
.      |                                        |               
.      | --- Signout Operation ---------------> | Authorization Server
.      |   Can not use Refresh Token to renew.  | (Revokes All Tokens)
.      |   Must use SRP to get fresh tokens.    | 
.      |   Current tokens remain valid until    | 
.      |   expiration. They have short validity.| 
.      |                                        |               
.      |                                        |               

Signature V4

var crypto = require("crypto-js");

function getSignatureKey(key, dateStamp, regionName, serviceName) {
    var kDate = crypto.HmacSHA256(dateStamp, "AWS4" + key);
    var kRegion = crypto.HmacSHA256(regionName, kDate);
    var kService = crypto.HmacSHA256(serviceName, kRegion);
    var kSigning = crypto.HmacSHA256("aws4_request", kService);
    return kSigning;
}
  • Create a canonical request.
  • Use the canonical request and additional metadata to for signing seed.
  • Derive a signing key from your AWS secret access key.
  • Then use the signing key, and the string from the previous step, to create a signature.

Example Signature:

POST https://iam.us-east-1.amazonaws.com/?Action=xyz&Version=2010-05-08 HTTP/1.1
Authorization: AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/cn-north-1
      /iam/aws4_request, SignedHeaders=content-type;host; x-amz-date,
      Signature=d37af66cc90d.....
content-type: application/x-www-form-urlencoded; charset=utf-8
host: iam.cn-north-1.amazonaws.com.cn.cn
x-amz-date: 20150830T123600Z
  • Add the resulting signature to the HTTP request in a header

Services which you may want to call with V4 signature

  • execute-api to invoke REST api call. Component service of API Gateway.
  • S3, ATS, Cognito??, Lambda, ...

Cognito RBAC (Rolebased Access Control) etc.

Cognito User pools support following kinds of Roles:

  • Default Unauthenticated
  • Default Authenticated Role
  • Choose Role From Rule:
    • Define user custom attributes e.g. Department
    • Create Roles like "Developer", "Tester", "Deployer"
    • Define Rules like if Department = 'QA', select Role 'Tester'
    • Onboarding of user just requires careful setting of custom attributes. Easier to debug since the "nature of user" is also reflected in user custom attributes.
  • Choose Role From Token:
    • Each user pool user is associated with set of Groups (with Precedents)
    • User automatically assumes the IAM Role of highest prcedent Group.
    • Onboarding of user just requires assigning user proper groups by hardcoded logic. This is harder to debug later if we want to trace why a specific user was assigned a group.
  • User pool is associated with multiple client IDs. You can have one clientID for SPA (configured to use SRP) and another client ID configure to use AWS IAM credentials (Admin).
  • Each userpool client APP can be configured to have read/write permission for specific user attributes.
  • API Gateway Authorization:
    • Cognito User Pools: User Pools Authorizer (built-in)
      • Submit Identity Token from User Pool (valid for 1 hour)
      • Invoke Lambda Function which runs with it's own execution credentials.
      • UserPool Groups and Custom Attributes are not used.
      • No fine-grained access control for normal vs admin user.
    • Cognito Federated Identities: AWS IAM Authorization (built-in)
      • Submit AWS Credentials to API Gateway.
      • Fetch associated policy for the IAM Role to verify if lambda function execution is included in the policy.
      • Fine grained access control.
    • Custom Identity Providers: Custom Authorizer (Lambda Function)
      • Auth0, Google, Company Active Directory - Identity Provider
      • Submit Identity Token From 3rd Party to API Gateway
      • Custom Authorizer checks with Id Provider and returns IAM Policy. (May use cache to derive effective policy)
      • API Gateway validates IAM Policy and invokes Lambda accordingly
  • App client specific configuration for authentication :
    • User Pool => App Integration => App Client Settings
    • You can choose identity providers e.g. Google, Okta, user-pool
    • Configure SignIn/SignOut callback support URLs e.g. http://mydomain.com:8100/callback The callback post request may give details such as the user ID etc.
    • OAuth 2.0 Flows supported can be selected. e.g. Authorization Code Grant
    • Also specify allowed OAuth scopes e.g. email, openid, profile (ie. Possibly restrict some scopes from set of all available scopes)
  • To link User Pool with external identity provider (for all Apps):
    • User Pool => Federation => Identity Providers
    • Enable FaceBook, Google, or any SAML provider like Okta
    • You can link Google Attributes to Userpool built-in/custom attributes
    • Each user must be able to specify what their linked federation identity, i.e. Google email or Facebook userid. This must be available through API call and configurable at user level not at Userpool level.

Wildrydes Application

Best Security and Perfomance Practices

AWS Services :

Cloudwatch    Monitoring and automation service for admins and dev.
CloudTrail    Operational auditing and compliance, logging.
              (Mainly to log API calls history for limited services only)
Glue          ETL and Crawlers can generate data catalogs.
Athena        SQL Service on top of S3
QuickSight    PowerBI and Tableu alternative
X-Ray         Distributed Tracing System. Works with Lambda, EC2, ECS,
              Elastic Bean Stack.
Macie         Security service using ML to protect sensitive data.
Kinesis       Stream Processing
Direct        Dedicated network connection from on-premise data 
 Connect      center to AWS VPC
EMR           Amazon Elastic MapReduce (EMR) is Apache Spark and Hadoop
Step Functions  Build distributed applications using visual workflows
  • Use Cloudwatch Events service pipe all events through lambda function, keep white list of events, store all suspicious events.
  • Amazon X-Ray service looking at cloud trails
  • Amazon Macie - Machine Learning based security advisor
  • 3 Components of Serverless Security - Services, Code, Data Flow
  • alexcasalboni/aws-lambda-power-tuning : Auto tunes memory for lambda
  • Eliminate unnecessary dependencies. Lambda cold start and warm start watch out.
  • Use Lambda env variables to change behaviour. Enable 'Active Tracing' which enables Amazon X-Ray to give you some profiling info.
  • AWS Serverless Application Model (SAM) is built on top of Cloud Formation. Useful for modeling and deployment. Cloud Formation and SAM tools ??? SAM-local i.e. awslabs/aws-sam-local helps to deploy local for testing.
  • Use CodePipeline to automate
  • Compress files with Splittable compression algo like bzip2

Cognito User pool CLI Reference

Synopsis :

aws cognito-idp <action> ....

Supported options are :

create-group
create-identity-provider
create-resource-server
create-user-import-job
create-user-pool
create-user-pool-client
create-user-pool-domain
admin-list-groups-for-user

list-groups
list-identity-providers
list-resource-servers
list-tags-for-resource
list-user-import-jobs
list-user-pool-clients
list-user-pools
list-users
list-users-in-group

add-custom-attributes
admin-add-user-to-group
admin-confirm-sign-up
admin-create-user
admin-delete-user
admin-delete-user-attributes
admin-disable-provider-for-user
admin-disable-user
admin-enable-user
admin-forget-device
admin-get-device
admin-get-user
admin-initiate-auth
admin-link-provider-for-user
admin-list-devices
admin-list-user-auth-events
admin-remove-user-from-group
admin-reset-user-password
admin-respond-to-auth-challenge
admin-set-user-mfa-preference
admin-set-user-settings
admin-update-auth-event-feedback
admin-update-device-status
admin-update-user-attributes
admin-user-global-sign-out
associate-software-token
change-password
confirm-device
confirm-forgot-password
confirm-sign-up
delete-group
delete-identity-provider
delete-resource-server
delete-user
delete-user-attributes
delete-user-pool
delete-user-pool-client
delete-user-pool-domain
describe-identity-provider
describe-resource-server
describe-risk-configuration
describe-user-import-job
describe-user-pool
describe-user-pool-client
describe-user-pool-domain
forget-device
forgot-password
get-csv-header
get-device
get-group
get-identity-provider-by-identifier
get-signing-certificate
get-ui-customization
get-user
get-user-attribute-verification-code
get-user-pool-mfa-config
global-sign-out
initiate-auth
list-devices
resend-confirmation-code
respond-to-auth-challenge
set-risk-configuration
set-ui-customization
set-user-mfa-preference
set-user-pool-mfa-config
set-user-settings
sign-up
start-user-import-job
stop-user-import-job
tag-resource
untag-resource
update-auth-event-feedback
update-device-status
update-group
update-identity-provider
update-resource-server
update-user-attributes
update-user-pool
update-user-pool-client
update-user-pool-domain
verify-software-token
verify-user-attribute

Cognito API Reference

User Pool API:

Identity Pool API:

Following User Pool actions are supported ...

  • CreateUserPool
  • CreateUserPoolClient (It is App Client)
  • CreateUserPoolDomain
  • AdminCreateUser, AdminDeleteUser, CreateGroup, DeleteGroup, UpdateGroup
  • AdminAddUserToGroup, AdminRemoveUserFromGroup
  • ListUsers, ListGroups, ListUsersInGroup, AdminListGroupsForUser
  • ChangePassword, AdminResetUserPassword
  • AdminInitiateAuth
  • AdminUpdateUserAttributes
  • AdminConfirmSignUp
  • AdminUserGlobalSignOut
  • GetIdentityProviderByIdentifier
  • GetUser
  • CreateIdentityProvider
  • AssociateSoftwareToken
  • AddCustomAttributes

Following Identity Pool actions are supported ... :

CreateIdentityPool, DeleteIdentityPool, UpdateIdentityPool, DescribeIdentityPool

GetId, DeleteIdentities, DescribeIdentity, ListIdentities, ListIdentityPools

GetCredentialsForIdentity

SetIdentityPoolRoles, GetIdentityPoolRoles

GetOpenIdToken, GetOpenIdTokenForDeveloperIdentity

LookupDeveloperIdentity, MergeDeveloperIdentities

TagResource, ListTagsForResource, UntagResource

UnlinkIdentity, UnlinkDeveloperIdentity
  • And more User Pool and Identity Pool API ... :

    AdminDeleteUserAttributes
    AdminDisableProviderForUser
    AdminDisableUser
    AdminEnableUser
    AdminForgetDevice
    AdminGetDevice
    AdminGetUser
    AdminLinkProviderForUser
    AdminListDevices
    AdminListUserAuthEvents
    AdminRespondToAuthChallenge
    AdminSetUserMFAPreference
    AdminSetUserSettings
    AdminUpdateAuthEventFeedback
    AdminUpdateDeviceStatus
    ConfirmDevice
    ConfirmForgotPassword
    ConfirmSignUp
    CreateResourceServer
    CreateUserImportJob
    DeleteIdentityProvider
    DeleteResourceServer
    DeleteUser
    DeleteUserAttributes
    DeleteUserPool
    DeleteUserPoolClient
    DeleteUserPoolDomain
    DescribeIdentityProvider
    DescribeResourceServer
    DescribeRiskConfiguration
    DescribeUserImportJob
    DescribeUserPool
    DescribeUserPoolClient
    DescribeUserPoolDomain
    ForgetDevice
    ForgotPassword
    GetCSVHeader
    GetDevice
    GetGroup
    GetSigningCertificate
    GetUICustomization
    GetUserAttributeVerificationCode
    GetUserPoolMfaConfig
    GlobalSignOut
    InitiateAuth
    ListDevices
    ListIdentityProviders
    ListResourceServers
    ListTagsForResource
    ListUserImportJobs
    ListUserPoolClients
    ListUserPools
    ResendConfirmationCode
    RespondToAuthChallenge
    SetRiskConfiguration
    SetUICustomization
    SetUserMFAPreference
    SetUserPoolMfaConfig
    SetUserSettings
    SignUp
    StartUserImportJob
    StopUserImportJob
    TagResource
    UntagResource
    UpdateAuthEventFeedback
    UpdateDeviceStatus
    UpdateIdentityProvider
    UpdateResourceServer
    UpdateUserAttributes
    UpdateUserPool
    UpdateUserPoolClient
    UpdateUserPoolDomain
    VerifySoftwareToken
    VerifyUserAttribute
    

Sending Post Signup Custom E-mail using Lambda Trigger

The signup event looks like below :

{
    "version": "1",
    "region": "ap-southeast-1",
    "userPoolId": "ap-southeast-1_userPoolId",
    "userName": "manhvu(=)orgos.net",
    "callerContext": {
        "awsSdkVersion": "aws-sdk-unknown-unknown",
        "clientId": "1cf6c9pm555ku9phsblle7o0eu"
    },
    "triggerSource": "CustomMessage_SignUp",
    "request": {
        "userAttributes": {
            "sub": "user-id-b6f2-19670da03c67",
            "email_verified": "false",
            "cognito:user_status": "UNCONFIRMED",
            "email": "manh.vv.htth@gmail.com"
        },
        "codeParameter": "{####}",
        "linkParameter": "{##Click Here##}",
        "usernameParameter": null
    },
    "response": {
        "smsMessage": null,
        "emailMessage": null,
        "emailSubject": null
    }
}

A lambda custom trigger may look like below :

module.exports.handler = (event, context, callback) => {
  // Identify why was this function invoked
  // console.log('---- event object', JSON.stringify(event));
  // console.log('---- context object', JSON.stringify(context));
  const {
    triggerSource
  } = event;

  if (triggerSource === 'CustomMessage_SignUp') {
    const {
      request: {
        userAttributes
      },
      response
    } = event;

    if (userAttributes['cognito:user_status'] === 'UNCONFIRMED') {
      const baseUrl = process.env.app__base_url;
      const confirmationPath = process.env.app__confirmation_path;
      const email = encodeURI(userAttributes.email);
      const confimationUrl = `${baseUrl}${confirmationPath}?email=${email}&confirmationCode={####}`;

      const emailMessage = `<h1> Hello ${email} </h1> <p> Here is activation link <a href="${confimationUrl}" target="_blank">Confirm your account</a> </p>`
      response.emailMessage = emailMessage;
      response.emailSubject = 'Confirm your CryptoBadge account';
    }
  }

  callback(null, event);
};

Note: The context has details such as cognito identity of the request. See https://docs.aws.amazon.com/lambda/latest/dg/nodejs-prog-model-context.html

Context Properties :

functionName      – The name of the Lambda function.
memoryLimitInMB   – The amount of memory configured on the function.
...

identity – (mobile apps) Information about the Amazon Cognito identity

  cognitoIdentityId – The authenticated Amazon Cognito identity.
  cognitoIdentityPoolId – Cognito identity pool that authorized the invocation.

clientContext – (mobile apps) Client context

  client.app_title
  client.app_version_name
  ...

CLI Example Sessions

aws cognito-idp create-user-pool --pool-name myblog
aws cognito-idp list-user-pools --max-results 10

    {
        "UserPools": [
            {
                "Id": "us-east-1_0Pe*****",
                "Name": "myblog",
                "LambdaConfig": {},
                "LastModifiedDate": 1527786443.052,
                "CreationDate": 1527786443.052
            }
        ]
    }

// The Id of the user pool is very important, we need it for reference.
// Note that user pool name is not unique across users, but Id is unique.

// Create a Resource Server

aws> cognito-idp create-resource-server 
  --name transactions --identifier transactions 
  --user-pool-id us-east-1_0Pe***** 
  --scopes ScopeName=get,ScopeDescription=get_tx 
           ScopeName=post,ScopeDescription=post_tx

    {
        "ResourceServer": {
            "UserPoolId": "us-east-1_0Pe*****",
            "Identifier": "transactions",
            "Name": "transactions",
            "Scopes": [
                {
                    "ScopeName": "post",
                    "ScopeDescription": "post_tx"
                },
                {
                    "ScopeName": "get",
                    "ScopeDescription": "get_tx"
                }
            ]
        }
    }

  // Create a Client App

aws> cognito-idp create-user-pool-client --user-pool-id us-east-1_0Pe*****
--allowed-o-auth-flows client_credentials --client-name test --generate-secret
--allowed-o-auth-scopes transactions/post
--allowed-o-auth-flows-user-pool-client

  {
      "UserPoolClient": {
          "UserPoolId": "us-east-1_0Pe*****",
          "ClientName": "test",
          "ClientId": "14aq5ll5b1it6f62uefe******",
          "ClientSecret": "j22a2ha9httcbord******e4k29ra7s8026agrc89nhjg******",
          "LastModifiedDate": 1527806667.264,
          "CreationDate": 1527806667.264,
          "RefreshTokenValidity": 30,
          "AllowedOAuthFlows": [
              "client_credentials"
          ],
          "AllowedOAuthScopes": [
              "transactions/post"
          ],
          "AllowedOAuthFlowsUserPoolClient": true
      }
  }

  // Note the ClientId and ClientSecret in the response
  // we’d need this to request an access token.

  // Add a Domain so we can get a URL for /oauth2/token endpoint.

aws> cognito-idp create-user-pool-domain  --domain lobster1234 --user-pool-id
     us-east-1_0Pe*****
    {
        "DomainDescription": {
            "UserPoolId": "us-east-1_0Pe*****",
            "AWSAccountId": "***431494***",
            "Domain": "lobster1234",
            "S3Bucket": "aws-cognito-prod-iad-assets",
            "CloudFrontDistribution": "d3oia8etllorh5.cloudfront.net",
            "Version": "20180531225618",
            "Status": "ACTIVE"
        }
    }


  // OAuth2 URL is https://lobster1234.auth.us-east-1.amazoncognito.com/oauth2/token

  // Get an Access Token

  // Notice that I am using HTTP Basic to send the client_id and client_secret.
  // This is base64(ClientId:ClientSecret).

$ AUTH_HEADER=$(echo -n 'ClientId:ClientSecret' | openssl base64)

$ curl -X POST \
  https://lobster1234.auth.us-east-1.amazoncognito.com/oauth2/token \
  -H 'authorization: Basic ********' \
  -H 'content-type: application/x-www-form-urlencoded' \
  -d 'grant_type=client_credentials&scope=transactions%2Fpost'

// This should return an access_token, which is a JWT.

{
    "access_token": "******************.eyJzdWIiO",
    "expires_in": 3600,
    "token_type": "Bearer"
}