Now let’s look at splittiing Cognito User Pool and Cognito Federated Identities into a separate Serverless service. In this chapter we are going to create a Serverless service that will use cross-stack references to tie all of our resources together.

In the example repo, open the auth service in the services/ directory.

service: notes-app-ext-auth

custom:
  # Our stage is based on what is passed in when running serverless
  # commands. Or fallsback to what we have set in the provider section.
  stage: ${opt:stage, self:provider.stage}

provider:
  name: aws
  stage: dev
  region: us-east-1

resources:
  Resources:
    CognitoUserPool:
      Type: AWS::Cognito::UserPool
      Properties:
        # Generate a name based on the stage
        UserPoolName: ${self:custom.stage}-ext-user-pool
        # Set email as an alias
        UsernameAttributes:
          - email
        AutoVerifiedAttributes:
          - email

    CognitoUserPoolClient:
      Type: AWS::Cognito::UserPoolClient
      Properties:
        # Generate an app client name based on the stage
        ClientName: ${self:custom.stage}-ext-user-pool-client
        UserPoolId:
          Ref: CognitoUserPool
        ExplicitAuthFlows:
          - ADMIN_NO_SRP_AUTH
        GenerateSecret: false

    # The federated identity for our user pool to auth with
    CognitoIdentityPool:
      Type: AWS::Cognito::IdentityPool
      Properties:
        # Generate a name based on the stage
        IdentityPoolName: ${self:custom.stage}ExtIdentityPool
        # Don't allow unathenticated users
        AllowUnauthenticatedIdentities: false
        # Link to our User Pool
        CognitoIdentityProviders:
          - ClientId:
              Ref: CognitoUserPoolClient
            ProviderName:
              Fn::GetAtt: [ "CognitoUserPool", "ProviderName" ]
              
    # IAM roles
    CognitoIdentityPoolRoles:
      Type: AWS::Cognito::IdentityPoolRoleAttachment
      Properties:
        IdentityPoolId:
          Ref: CognitoIdentityPool
        Roles:
          authenticated:
            Fn::GetAtt: [CognitoAuthRole, Arn]
            
    # IAM role used for authenticated users
    CognitoAuthRole:
      Type: AWS::IAM::Role
      Properties:
        Path: /
        AssumeRolePolicyDocument:
          Version: '2012-10-17'
          Statement:
            - Effect: 'Allow'
              Principal:
                Federated: 'cognito-identity.amazonaws.com'
              Action:
                - 'sts:AssumeRoleWithWebIdentity'
              Condition:
                StringEquals:
                  'cognito-identity.amazonaws.com:aud':
                    Ref: CognitoIdentityPool
                'ForAnyValue:StringLike':
                  'cognito-identity.amazonaws.com:amr': authenticated
        Policies:
          - PolicyName: 'CognitoAuthorizedPolicy'
            PolicyDocument:
              Version: '2012-10-17'
              Statement:
                - Effect: 'Allow'
                  Action:
                    - 'mobileanalytics:PutEvents'
                    - 'cognito-sync:*'
                    - 'cognito-identity:*'
                  Resource: '*'
                # Allow users to upload attachments to their
                # folder inside our S3 bucket
                - Effect: 'Allow'
                  Action:
                    - 's3:*'
                  Resource:
                    - Fn::Join:
                      - ''
                      -
                        - 'Fn::ImportValue': ${self:custom.stage}-ExtAttachmentsBucketArn
                        - '/private/'
                        - '$'
                        - '{cognito-identity.amazonaws.com:sub}/*'

  # Print out the Id of the User Pool and Identity Pool that are created
  Outputs:
    UserPoolId:
      Value:
        Ref: CognitoUserPool

    UserPoolClientId:
      Value:
        Ref: CognitoUserPoolClient

    IdentityPoolId:
      Value:
        Ref: CognitoIdentityPool

    CognitoAuthRole:
      Value:
        Ref: CognitoAuthRole
      Export:
        Name: ExtCognitoAuthRole-${self:custom.stage}

This can seem like a lot but both the CognitoUserPool: and the CognitoUserPoolClient: section are simply creating our Cognito User Pool. And you’ll notice that both these sections are not using any cross-stack references. They are effectively standalone. If you are looking for more details on this, refer to the earlier part of this guide.

Cognito Identity Pool

The Cognito Identity Pool on the other hand needs to reference all the resources created thus far. It can be a little intimidating to start but let’s break it down into its various parts:

  • CognitoIdentityPool: creates the role and states that the Cognito User Pool that we created above is going to be our auth provider.

  • The Identity Pool has an IAM role attached to its authenticated and unauthenticated users. Since, we only allow authenticated users to our note taking app; we only have one role. The CognitoIdentityPoolRoles: section states that we have an authenticated user role that we are going to create below and we are referencing it here by doing Fn::GetAtt: [CognitoAuthRole, Arn].

  • Finally, the CognitoAuthRole: section creates the IAM role that will allow access to our S3 file uploads bucket.

Let’s look at the Cognito auth IAM role in detail.

Cognito Auth IAM Role

The IAM role that our authenticated users are going to use needs to allow access to our S3 file uploads bucket.

This is the relevant section from the above serverless.yml.

# Allow users to upload attachments to their
# folder inside our S3 bucket
- Effect: 'Allow'
  Action:
    - 's3:*'
  Resource:
    - Fn::Join:
      - ''
      -
        - 'Fn::ImportValue': ${self:custom.stage}-ExtAttachmentsBucketArn
        - '/private/'
        - '$'
        - '{cognito-identity.amazonaws.com:sub}/*'

The S3 bucket resource in our IAM role looks something like:

"arn:aws:s3:::my_s3_bucket/private/${cognito-identity.amazonaws.com:sub}/*"

Where my_s3_bucket is the name of the bucket. We are going to use the generated name that we exported back in the S3 bucket chapter. And we can import it using:

'Fn::ImportValue': ${self:custom.stage}-ExtAttachmentsBucketArn

Again, all of our references are based on the stage we are deploying to. You’ll notice that we don’t have the IAM role here that allows access to our APIs. We are going to be doing that along side our API services.

And finally, you’ll notice that we are outputting:

  • A couple of things in this service. We need the Ids of the Cognito resources created in our frontend. But we don’t have to export any cross-stack values.
  • The name of the CognitoAuthRole IAM role. We need it in our api service to grant permissions for the role to invoke the API Gateway endpoint that will be deployed.

Now that all of our resources are complete, let’s look at the API services.