Cognito

Add AAA (User Authentication, Authorization and Accounting)
Share this page:

If you’ve just landed here, we’re doing a “Become a Cloud Architect” Unicorn Workshop by building a Unicorn Pursuit Web App step by step, and you’re more then welcome to join!

About Cognito

Amazon Cognito provides authentication, authorization, and user management for your web and mobile apps. Your users can sign in directly with a user name and password, or through a third party such as Facebook, Amazon, Google or Apple.

The two main components of Amazon Cognito are:

  • User pools, as directories that provide sign-up and sign-in options for your app users. User Pool makes it easy for developers to add sign-up and sign-in functionality to web and mobile applications. It serves as your own identity provider to maintain a user directory. It supports user registration and sign-in, as well as provisioning identity tokens for signed-in users.
  • Identity pools, which enable you to grant your users access to other AWS services.

For Unicorn Pursuit, we need to limit our “voting body” by email, so we need a User Pool, and ideally we’ll enable people from our email domain to auto-register.

User pools can either be configured so that user name is primary sign in form, but also allows for the other parameters, such as email address or a “preferred name” to be used additionally; or it can be configured so that email and/or phone numbers are the only ways a user can register and sign in.

Attributes represent the various properties of each user that’s collected and stored in the user pool. Cognito provides a set of standard attributes that are available for all user pools. Users are allowed to select any of these standard attributes to be required. Users will not be able to sign up to the user pool without providing the required attributes. Besides these, additional attributes can be further defined, and are known as custom attributes.

Cognito sends emails to users in the user pool, when particular actions take place, such as welcome emails, invitation emails, password resets, etc. By default, user pools are configured to use Cognito built-in email capability, but it can also be configured to use Amazon SES, however, support for Amazon SES is not available in the CDK yet (May 2020).

App Client: an app is an entity within a user pool that has permission to call unauthenticated APIs (APIs that do not have an authenticated user), such as APIs to register, sign in, and handle forgotten passwords. To call these APIs, you need an app client ID and an optional client secret.

After setting up an app client, the address for the user pool’s sign-up and sign-in webpages can be configured using domains. There are two ways to set up a domain:

  • Amazon Cognito hosted domain can be chosen with an available domain prefix.
  • Custom domain name can be chosen. The custom domain must be one that is already owned, and whose certificate is registered in AWS Certificate Manager, as described here.

Code

Before we get into Code, let’s understand what we’re building. Check out the diagram below:

Cognito CloudFront Route53

Requirements

We need to configure a User Pool, and we want our users to sign up using their corporate email and a user name. An email needs to be sent to users with a confirmation link, before they can access the web.

Attributes the user needs to give us when signing up are:

  • email address
  • username
  • full name

Users policy needs to be at least 12 characters long, and needs to include upper and lower case, numbers and symbols.

We then need to configure an App Client, and associate it with our Unicorn User Pool.

There are 2 options:

  • Hosted UI, recommended to quickly “integrate” Congito UI in your Frontend, if you’re using VUE, React or Angular.
  • Custom UI, that we’d make on our Frontend.

Since Cognito is a very important service, we will cover both options here. For Unicorn Pursuit, we will be using the Option 2 - Custom UI.

Configuration - Hosted UI

Don’t forget to ADD the Cognito to the requirements.txt before importing to the library.

pip install aws-cdk.aws-cognito
pip install -r requirements.txt
pip freeze > requirements.txt

Ok, let’s go to our iac_stack.py and modify the imports:

from aws_cdk import (
    aws_s3 as s3,
    aws_dynamodb as ddb,
    aws_cognito as cognito,
    core
)

We will be using CDK AWS Python UserPool Cognito class.

Ideally, we would have some kind of SSO, like AWS SSO o using Azure AD, but in this case, we’ll assume we’re using this app a bit more “openly”, and we won’t to create a separate User Pool.

By using an Amazon Cognito user pool, you can create and maintain a user directory, and add sign-up and sign-in to your mobile app or web application.

By default, self sign up is disabled, but for Unicorn Pursuit we do want our users to sign up… and we’ll spice it up with some security by only allowing certain email extensions. So, to sum up:

  • We’re creating a user pool manually, adding domain check as security method.
  • We will require users name and email address. We can use an email as a username.
  • Password policy, as per requirements.

Let’s out the cdk diff, to see which new resources we’re creating:

CDK DIFF

Looks all right, let’s go for cdk deploy, and observe:

Cognito User Pool Created

Cool, let’s check the results:

aws cognito-idp list-user-pools --max-results 20

UserPools:
- CreationDate: '2020-05-04T16:25:50.578000+02:00'
  Id: eu-west-1_3ryklNgoR
  LambdaConfig: {}
  LastModifiedDate: '2020-05-04T16:25:50.578000+02:00'
  Name: CognitoUnicornUserPool

All good, User Pool created! Let’s now go for App Client, and then Domain.

client = userpool.add_client("UnicornAppClient")
client_id = client.user_pool_client_id

Cool, so good so far… now let’s make sure to actually add client to the User Pool. This here is THE Ninja move of Cognito, simply because you have so many options in Cognito on how to authenticate and later authorize.

## Cognito: Create App Client & create Authentication Flow with User and Password
client = userpool.add_client(
"UnicornAppClient",
user_pool_client_name="UnicornAppClient",
generate_secret=False,
auth_flows=cognito.AuthFlow(
    admin_user_password=False,
    custom=False,
    refresh_token=True,
    user_password=False,
    user_srp=False
    ),
o_auth=cognito.OAuthSettings(

   ## We'll allow both Flows, Implicit and Authorization Code, and decide in the app which to use.
   flows=cognito.OAuthFlows(
  authorization_code_grant=True,
  implicit_code_grant=True,
   ),

   ## If you don't have any preferences, it's all right to allow all scopes.
   scopes=[cognito.OAuthScope.EMAIL,cognito.OAuthScope.OPENID,cognito.OAuthScope.PHONE,cognito.OAuthScope.PROFILE,cognito.OAuthScope.COGNITO_ADMIN],

   ## We need to define our CALLBACK URL, meaning - where our users are redirected when authenticated.
   callback_urls=["https://www.unicornpursuit.com"]
    )
)
(.env)iac git:(dev)cdk diff
Stack UnicornIaC
Resources
[+] AWS::Cognito::UserPoolClient CognitoUnicornUserPool/UnicornAppClient CognitoUnicornUserPoolUnicornAppClientA01194FB

We can cdk deploy this quickly, and proceed with the domain. This one might get a bit more complex, if you’re interesting in using your own domain (custom domain).

If you’re fine with using cognito-assigned URL with a custom prefix, this is the code:

userpool.add_domain("CognitoDomain",
    cognito_domain={
   "domain_prefix": "unicornpursuit"
    }
)

However, we’re going for “auth.unicornpursuit.com”, as we’re building something ready for production, so before we configure, we need to make sure:

  • Subdomain exists (if you’re using Route 53, you need to create a new hosted zone called auth.yourdomain.com, and copy the NS record from the auth to the root)
  • You have a SSL for either a wildcard (*.yourdomain.com) or a specific auth.yourdomain.com. In AWS Certificate Manager, you can use a single SSL for both, root and the wildcard, and that’s what we did here.

If we use Certificate Manager, we need to make sure Certificate Manager CDK library is in our requirements.txt file, and imported to our iac_stack.py, which makes the import like this for the moment:

from aws_cdk import (
    aws_s3 as s3,
    aws_dynamodb as ddb,
    aws_cognito as cognito,
    aws_certificatemanager as acm,
    core
)

When in Certificate Manager, be sure to add both root and wildcard:

Subdomain Added

If your domain is on Route53, you can just click on “create a record”. If not - you need to manually create a CNAME, and copy/paste the values.

SSL OK

Copy certificate ARN, and create a new variable certificate_arn, as shown in the code below:

certificate_arn="PASTE_YOUR_SSL_ARN_HERE"
domain_cert = acm.Certificate.from_certificate_arn(self, "domainCert", certificate_arn)

userpool.add_domain("UnicornPursuitDomain",
  custom_domain={
    "domain_name": "auth.unicornpursuit.com",
    "certificate": domain_cert
  }
)

Before you can use this domain, you must add the alias target to your domain’s DNS record. If you’re using Amazon Route 53 to manage your domain, you can do that from the Route 53 console. If you go to Cognito User Pools - App Integration - Domain Name, you will be given the Target you need to configure to Route53 (or where ever your domain is hosted) ALIAS A Record. Only once you do this, you’ll be able to go to the AUTH web page… and even though you won’t get any content, you will make sure SSL is in place:

SSL is Valid

Ok, all good… let’s deploy, shall we?

ERROR

Why are we getting this error? Because at the moment, our root domain unicornpursuit.com doesn’t have an A record, so it doesn’t really “point” anywhere, so the subdomain auth.unicornpursuit.com also cannot “operate”.

How do we solve it? We have 2 options:

Time to cdk synth our CloudFormation Template. The entire Cognito part looks like this:

...

  CognitoUnicornUserPoolsmsRoleD1ABE037:
    Type: AWS::IAM::Role
    Properties:
 AssumeRolePolicyDocument:
   Statement:
     - Action: sts:AssumeRole
  Condition:
    StringEquals:
 sts:ExternalId: UnicornIaCCognitoUnicornUserPool358A89E2
  Effect: Allow
  Principal:
    Service: cognito-idp.amazonaws.com
   Version: "2012-10-17"
 Policies:
   - PolicyDocument:
  Statement:
    - Action: sns:Publish
 Effect: Allow
 Resource: "*"
  Version: "2012-10-17"
     PolicyName: sns-publish
 Tags:
   - Key: project
     Value: unicorn
   - Key: bu
     Value: cloud
   - Key: environment
     Value: prod
    Metadata:
 aws:cdk:path: UnicornIaC/CognitoUnicornUserPool/smsRole/Resource
  CognitoUnicornUserPool1C6E3F95:
    Type: AWS::Cognito::UserPool
    Properties:
 AdminCreateUserConfig:
   AllowAdminCreateUserOnly: false
 AliasAttributes:
   - email
 AutoVerifiedAttributes:
   - email
 Policies:
   PasswordPolicy:
     MinimumLength: 12
     RequireLowercase: true
     RequireNumbers: true
     RequireSymbols: true
     RequireUppercase: true
 Schema:
   - Name: email
     Required: true
   - Name: name
     Required: true
 SmsConfiguration:
   ExternalId: UnicornIaCCognitoUnicornUserPool358A89E2
   SnsCallerArn:
     Fn::GetAtt:
  - CognitoUnicornUserPoolsmsRoleD1ABE037
  - Arn
 SmsVerificationMessage: The verification code to your new account is {####}
 UserPoolName: CognitoUnicornUserPool
 UserPoolTags:
   project: unicorn
   bu: cloud
   environment: prod
 VerificationMessageTemplate:
   DefaultEmailOption: CONFIRM_WITH_LINK
   EmailMessageByLink: Hello {username}. Welcome to Unicorn Pursuit! Follow the link {##Verify Email##} to confirm your email address.
   EmailSubjectByLink: "Unicorn Pursuit: Verify your email"
   SmsMessage: The verification code to your new account is {####}
    Metadata:
 aws:cdk:path: UnicornIaC/CognitoUnicornUserPool/Resource
  CognitoUnicornUserPoolUnicornAppClientA01194FB:
    Type: AWS::Cognito::UserPoolClient
    Properties:
 UserPoolId:
   Ref: CognitoUnicornUserPool1C6E3F95
 ClientName: UnicornAppClient
 ExplicitAuthFlows:
   - ALLOW_USER_PASSWORD_AUTH
   - ALLOW_REFRESH_TOKEN_AUTH
    Metadata:
 aws:cdk:path: UnicornIaC/CognitoUnicornUserPool/UnicornAppClient/Resource
  CognitoUnicornUserPoolUnicornPursuitDomainF41B2E5C:
    Type: AWS::Cognito::UserPoolDomain
    Properties:
 Domain: auth.unicornpursuit.com
 UserPoolId:
   Ref: CognitoUnicornUserPool1C6E3F95
 CustomDomainConfig:
   CertificateArn: arn:aws:acm:us-east-1:TEXT_NOT_DISCLOSED
    Metadata:
 aws:cdk:path: UnicornIaC/CognitoUnicornUserPool/UnicornPursuitDomain/Resource
  CDKMetadata:
    Type: AWS::CDK::Metadata

Once unicornpursuit.com is “operational”, either the “temporary"S3 + CloudFront Version or the final one, the CDK deploy goes smoothly:

SUCCESS

Awesome!!!

Configuration - Custom UI

Let’s say we first did an Option 1 for exercise, which I would totally recommend. We first need to delete our Cognito Resources, and then create a fresh new user pool with USER_PASSWORD and TOKEN options.

Deleted

Ok, so the new plan is to create a User Pool, and Client App that allows us to Sign Up, confirm our Phone Number using OTP (One Time Password), and Sign In. All the User Pool TOKENs will be handled in our Golang code.

The new Python code would look like this:


   # Cognito: Create User Pool
   userpool = cognito.UserPool(
  self, "CognitoUnicornUserPool",
  user_pool_name="CognitoUnicornUserPool",
  self_sign_up_enabled=True,
  
  ## Require username or email for users to sign in
  sign_in_aliases=cognito.SignInAliases(
      username=False,
      email=True,
  ),
  # Require users to give their full name when signing up
  required_attributes=cognito.RequiredAttributes(
      fullname=True,
      email=True,
      phone_number=True
  ),
  # Verify new sign ups using email
  auto_verify=cognito.AutoVerifiedAttrs(
      email=False,
      phone=True,
  ),
  # Configure OTP Settings ()
  user_verification=cognito.UserVerificationConfig(
      sms_message="Hey Unicorn Hunter, welcome to Unicorn Pursuit! Your OTP is {####}",
  ),
  # Set up required password policy
  password_policy=cognito.PasswordPolicy(
      min_length=12,
      require_symbols=True,
      require_lowercase=True,
      require_uppercase=True,
      require_digits=True,
  )
   )

   ## Cognito: Create App Client & create Authentication Flow with User and Password
   client = userpool.add_client(
  "UnicornAppClient",
  user_pool_client_name="UnicornAppClient",
  generate_secret=False,
  
  ## We'll allow both Flows, Implicit and Authorization Code, and decide in the app which to use.
  auth_flows=cognito.AuthFlow(
      admin_user_password=False,
      custom=False,
      refresh_token=True,
      user_password=True,
      user_srp=False
      ),
   )

CDK tells us the differences:

Option 2 CDK DIFF

And the synthesized CloudFormation Template:

  CognitoUnicornUserPool1C6E3F95:
    Type: AWS::Cognito::UserPool
    Properties:
 AdminCreateUserConfig:
   AllowAdminCreateUserOnly: false
 AliasAttributes:
   - email
 AutoVerifiedAttributes:
   - phone_number
 EmailVerificationMessage: Hello {username}, Your verification code is {####}
 EmailVerificationSubject: Verify your new account
 Policies:
   PasswordPolicy:
     MinimumLength: 12
     RequireLowercase: true
     RequireNumbers: true
     RequireSymbols: true
     RequireUppercase: true
 Schema:
   - Name: email
     Required: true
   - Name: name
     Required: true
   - Name: phone_number
     Required: true
 SmsConfiguration:
   ExternalId: UnicornIaCCognitoUnicornUserPool358A89E2
   SnsCallerArn:
     Fn::GetAtt:
  - CognitoUnicornUserPoolsmsRoleD1ABE037
  - Arn
 SmsVerificationMessage: Hey {username}, welcome to Unicorn Pursuit! Your OTP is {####}
 UserPoolName: CognitoUnicornUserPool
 UserPoolTags:
   project: unicorn
   bu: cloud
   environment: prod
 VerificationMessageTemplate:
   DefaultEmailOption: CONFIRM_WITH_CODE
   EmailMessage: Hello {username}, Your verification code is {####}
   EmailSubject: Verify your new account
   SmsMessage: Hey {username}, welcome to Unicorn Pursuit! Your OTP is {####}
    Metadata:
 aws:cdk:path: UnicornIaC/CognitoUnicornUserPool/Resource
  CognitoUnicornUserPoolUnicornAppClientA01194FB:
    Type: AWS::Cognito::UserPoolClient
    Properties:
 UserPoolId:
   Ref: CognitoUnicornUserPool1C6E3F95
 ClientName: UnicornAppClient
 ExplicitAuthFlows:
   - ALLOW_USER_PASSWORD_AUTH
   - ALLOW_REFRESH_TOKEN_AUTH
 GenerateSecret: false
    Metadata:
 aws:cdk:path: UnicornIaC/CognitoUnicornUserPool/UnicornAppClient/Resource
  CDKMetadata:
    Type: AWS::CDK::Metadata
    Properties:

Where to find more info




Last modified June 8, 2020: AWS Diagrams Added (c98aff0)