Cognito as a Serverless Service
Now that we have all of our resources created (API, uploads, database), let’s secure them using Cognito User Pool as an authentication provider and Cognito Federated Identities to control access. 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-mono-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
runtime: nodejs8.10
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}-mono-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}-mono-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}MonoIdentityPool
# 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 invoke our API
- Effect: 'Allow'
Action:
- 'execute-api:Invoke'
Resource:
Fn::Join:
- ''
-
- 'arn:aws:execute-api:'
- Ref: AWS::Region
- ':'
- Ref: AWS::AccountId
- ':'
- 'Fn::ImportValue': ${self:custom.stage}-ApiGatewayRestApiId
- '/*'
# Allow users to upload attachments to their
# folder inside our S3 bucket
- Effect: 'Allow'
Action:
- 's3:*'
Resource:
- Fn::Join:
- ''
-
- 'Fn::ImportValue': ${self:custom.stage}-AttachmentsBucketArn
- '/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
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 Part II 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 doingFn::GetAtt: [CognitoAuthRole, Arn]
. -
Finally, the
CognitoAuthRole:
section creates the IAM role that will allow access to our API and 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 API Gateway resource and our S3 file uploads bucket.
This is the relevant section from the above serverless.yml
.
# Allow users to invoke our API
- Effect: 'Allow'
Action:
- 'execute-api:Invoke'
Resource:
Fn::Join:
- ''
-
- 'arn:aws:execute-api:'
- Ref: AWS::Region
- ':'
- Ref: AWS::AccountId
- ':'
- 'Fn::ImportValue': ${self:custom.stage}-ApiGatewayRestApiId
- '/*'
# Allow users to upload attachments to their
# folder inside our S3 bucket
- Effect: 'Allow'
Action:
- 's3:*'
Resource:
- Fn::Join:
- ''
-
- 'Fn::ImportValue': ${self:custom.stage}-AttachmentsBucketArn
- '/private/'
- '$'
- '{cognito-identity.amazonaws.com:sub}/*'
The API Gateway resource in our IAM role looks something like:
arn:aws:execute-api:us-east-1:12345678:qwe123rty456/*
Where us-east-1
is the region, 12345678
is the AWS account Id, and qwe123rty456
is the API Gateway Resource Id. To construct this dynamically we need the cross-stack reference of the API Gateway Resource Id that we exported in the API Gateway chapter. And we can import it like so:
'Fn::ImportValue': ${self:custom.stage}-ApiGatewayRestApiId
Again, all of our references are based on the stage we are deploying to.
The S3 bucket on the other hand has a resource that 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}-AttachmentsBucketArn
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.
Now that all of our resources are complete, we’ll look at how to deploy them. There is a bit of a wrinkle here since we have some dependencies between our services.
For help and discussion
Comments on this chapterFor reference, here is the code we are using
Mono-repo Backend SourceIf you liked this post, please subscribe to our newsletter, give us a star on GitHub, and follow us on Twitter.