Cognito User/Identity Pools as serverless.yml resource defs

@altcatalin Thanks for the idea, but it still barfs on it on a new line.

@pgali The environment variable can be read in the function no problem, but I’m trying to get the framework to set up the authorizer for me automatically. That may be asking to much, I’m beginning to think 8).

have you also tried with the long function form?
http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-getatt.html

this works

functions:
  ConsumerFunction:
    ...
    environment:
      QUEUE_ARN:
        Fn::GetAtt:
          - Queue
          - Arn

resources:  # CloudFormation template syntax
  Resources:

    # SQS
    Queue:
      Type: AWS::SQS::Queue
      ...

FYI, just got confirmation that this is a bug that will be fixed as part of a larger ARN issue, workaround described here:

Basically, create your stack once, come back to your serverless.yml and hard code the ARN that gets generated.

I found this gist on github which helped solve a lot of my problems: https://gist.github.com/singledigit/2c4d7232fa96d9e98a3de89cf6ebe7a5

I’ve gotten this to work with text message verification using gist from @barticus… but how it works with email verification I am not sure.

Has anyone found out more about this. I’m looking to do the exact same thing you @nerdguru.

Can’t see to get the authorizer to take in a GetAtt call to the resource. I keep getting the SLS error
functionArn.split is not a function

I was able to get serverless to generate my identity pool using the following:

IdentityPool:
  Type: "AWS::Cognito::IdentityPool"
  Properties:
    IdentityPoolName: identitypoolname
    AllowUnauthenticatedIdentities: true
    CognitoIdentityProviders: 
      - ClientId:
          Ref: UserPoolClient
        ProviderName:
          'Fn::GetAtt': [ MyUserPool, ProviderName ]

Hope this helps

This will setup userpools that are compatible with aws-amplify

service:
  name: digiStatic

frameworkVersion: ">=1.1.0 <2.0.0"

provider:
  name: aws
  runtime: nodejs6.10
  memorySize: 512
  # Serverless variables like ${self:custom.prefix} We use variableSyntax here
  # to change serverless vars to look instead like $<self:custom.prefix>
  variableSyntax: '\$<([ :a-zA-Z0-9._,\\-\\/\\(\\)]+?)>'

plugins:
  - serverless-stack-output

custom:
  output:
    handler: scripts/output.handler # Same syntax as you already know
    file: ../stack.json # toml, yaml, yml, and json format is available

resources:
  Resources:

    # Creates a user pool in cognito for your app to auth against

    # Fixme: customer email address needs to be manually entered and verified
    # https://console.aws.amazon.com/ses/home?region=us-east-1#verified-senders-email:

    # Time based MFA can not be enabled from script
    # I have asked AWS to fix this

    UserPool:
      Type: "AWS::Cognito::UserPool"
      Properties:
        UserPoolName: $<self:service>-user-pool
        SmsVerificationMessage: "Your verification code is {####}."
        AutoVerifiedAttributes:
          - email
        MfaConfiguration: "OFF"
        EmailVerificationSubject: "Your Digispeaker verification code"
        EmailVerificationMessage: "Your Digispeaker verification code is {####}."
        SmsAuthenticationMessage: "Your Digispeaker authentication code is {####}."
        Schema:
          - Name: name
            AttributeDataType: String
            Mutable: true
            Required: false
          - Name: email
            AttributeDataType: String
            Mutable: false
            Required: true
          - Name: phone_number
            AttributeDataType: String
            Mutable: true
            Required: false
        Policies:
          PasswordPolicy:
            RequireLowercase: true
            RequireSymbols: false
            RequireNumbers: true
            MinimumLength: 8
            RequireUppercase: true
        AdminCreateUserConfig:
          InviteMessageTemplate:
            EmailMessage: "Your Digispeaker username is {username} and temporary password is {####}."
            EmailSubject: "Your temporary Digispeaker password"
            SMSMessage: "Your Digispeaker username is {username} and temporary password is {####}."
          UnusedAccountValidityDays: 7
          AllowAdminCreateUserOnly: false

    # Creates a User Pool Client to be used by the identity pool
    UserPoolClient:
      Type: "AWS::Cognito::UserPoolClient"
      Properties:
        ClientName: $<self:service>-client
        GenerateSecret: false
        UserPoolId: 
          Ref: UserPool
    
    # Creates a federeated Identity pool
    IdentityPool:
      Type: "AWS::Cognito::IdentityPool"
      Properties:
        IdentityPoolName: $<self:service>Identity
        AllowUnauthenticatedIdentities: true
        CognitoIdentityProviders: 
          - ClientId: 
              Ref: UserPoolClient
            ProviderName:
              'Fn::GetAtt': [ UserPool, ProviderName ]
        SupportedLoginProviders:
          'graph.facebook.com': "xxxxxxxxxx"
        OpenIdConnectProviderARNs:
          - 'arn:aws:iam::xxxxxxxxxxx:oidc-provider/accounts.google.com'

    # Create a role for unauthorized acces to AWS resources. Very limited access. Only allows users in the previously created Identity Pool
    CognitoUnAuthorizedRole:
      Type: "AWS::IAM::Role"
      Properties:
        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: IdentityPool
                "ForAnyValue:StringLike":
                  "cognito-identity.amazonaws.com:amr": unauthenticated
        Policies:
          - PolicyName: "CognitoUnauthorizedPolicy"
            PolicyDocument: 
              Version: "2012-10-17"
              Statement: 
                - Effect: "Allow"
                  Action:
                    - "mobileanalytics:PutEvents"
                    - "cognito-sync:*"
                  Resource: "*"

    # Create a role for authorized acces to AWS resources. Control what your user can access. This example only allows Lambda invokation
    # Only allows users in the previously created Identity Pool
    CognitoAuthorizedRole:
      Type: "AWS::IAM::Role"
      Properties:
        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: IdentityPool
                "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: "*"
                - Effect: "Allow"
                  Action:
                    - "lambda:InvokeFunction"
                  Resource: "*"
    
    # Assigns the roles to the Identity Pool
    IdentityPoolRoleMapping:
      Type: "AWS::Cognito::IdentityPoolRoleAttachment"
      Properties:
        IdentityPoolId: 
          Ref: IdentityPool
        Roles:
          authenticated:
              'Fn::GetAtt': [ CognitoAuthorizedRole, Arn ]
          unauthenticated:
              'Fn::GetAtt': [ CognitoUnAuthorizedRole, Arn ]

  Outputs:
    UserPoolId:
      Value: 
        Ref: UserPool
      Export:
        Name: "UserPool::Id"
    UserPoolClientId:
      Value:
        Ref: UserPoolClient
      Export:
        Name: "UserPoolClient::Id"
    IdentityPoolId:
      Value:
        Ref: IdentityPool
      Export:
        Name: "IdentityPool::Id"
9 Likes

Thanks @jonsmirl - your example has been extremely helpful to me. Any chance you could tell me what I’d need to change to configure the User Pool resource so that it is set up to use the user’s email address as their “username”. I can do this via the AWS console but I want to put this config in serverless.yml.

I think you remove the ‘name’ field. But does amplify support that yet? I don’t think they have gotten around to making the userpool stuff configurable yet.

I don’t really know much about amplify. I just want to add the config for my user pool to my serverless service and I was able to take what I needed from your example and use that to create a user pool successfully, just not with the exact config I need.

The ‘name’ field is different to ‘username’ so I don’t think removing that will achieve what I want. Are you able to point me in direction of any docs/tutorials that might help me out? Thanks again!

Use the UI to get it the way you like it. Then use the CLI to describe it.

aws cognito-idp describe-user-pool --user-pool-id xxxxxxx

4 Likes

Ah ha! Didn’t know I could do that. Thanks, that’s super helpful.

hi all
so i want use cognito pool id use in lambda function.

i try to import UserPool::Id to env varible and then use env varible inside lambda. but its not work
got a error :circuiler dependency error within serverless deploy

Is there any update on the issue? I still want to reference the user pool using the ARN dynamically.

Something that caught me out on this – Cognito Identity Pools cannot have hyphens in the pool name (unlike user pools and many other named elements).

I was getting a regex error when trying to deploy:
1 validation error detected: Value 'XXXXX; at ‘identityPoolName’ failed to satisfy constraint: Member must satisfy regular expression pattern: [\w ]+ (Service: AmazonCognitoIdentity; Status Code: 400;

Removed the hyphens from the name and it deployed without issue:
‘’’
CognitoIdentityPool:
Type: AWS::Cognito::IdentityPool
Properties:
IdentityPoolName: ${self:custom.stage}SomeNameIdentityPool
AllowUnauthenticatedIdentities: false
CognitoIdentityProviders:
- ClientId:
Ref: CognitoUserPoolClient
ProviderName:
‘Fn::GetAtt’: [“CognitoUserPool”,“ProviderName”]

‘’’

1 Like

Have a look here, may help you, unless you provide a string to arn, the resource build in serverless assumes you’re trying to reference a lambda function to make the authorization, and can only build that kind of authorizer for you: https://github.com/serverless/serverless/blob/master/lib/plugins/aws/package/compile/events/apiGateway/lib/authorizers.js#L27

I managed to dynamically build an Authorizer reference like this:

1 Like
2 Likes

Thanks, you’re a lifesaver :slight_smile:

1 Like

This is great, thanks @jonsmirl!

It looks like this might not facilitate multiple environments, however, as the error I’m getting seems to be from the ‘Outputs’:

service-prod - Export with name UserPoolClient::Id is already exported by stack service-pre.

The only alternative I can think of doing, since we cannot use strings as key/property names in YAML, perhaps exporting both versions of pre/prod:

Outputs:
    UserPoolIdpre:
      Value:
        Ref: UserPoolpre
      Export:
        Name: 'UserPool::Id'
    UserPoolIdprod:
      Value:
        Ref: UserPoolprod
      Export:
        Name: 'UserPool::Id'
UserPoolClient:
  Type: 'AWS::Cognito::UserPoolClient'
  Properties:
    ClientName: service-${self:provider.stage}-web-client
    GenerateSecret: false
    UserPoolId:
      Ref: "UserPool${self:provider.stage}"

Has anyone else figured this one out because this is not scalable, looks ugly and probably doesn’t even work?