Cognito

Cognito

The AWS Cognito service enables you to manage authentication and access control for AWS-backed apps and resources.

LocalStack Pro contains basic support for authentication via Cognito. You can create Cognito user pools, sign up and confirm users, set up Lambda triggers, and use the COGNITO_USER_POOLS authorizer integration with API Gateway.

Note: By default, local Cognito does not send actual email messages. To enable this feature, you will require an email address and the corresponding SMTP settings. Please refer to the Configuration guide for instructions on how to configure the connection parameters of your SMTP server.

User pools and basic authentication flows

The following subsections illustrate how you can create a user pool and client, and then sign up and authenticate a new user in the pool.

Creating a User Pool

Just as with AWS, you can create a user pool in LocalStack with the following command:

  1. $ awslocal cognito-idp create-user-pool --pool-name test

The response should look similar to this:

  1. "UserPool": {
  2. "Id": "us-east-1_fd924693e9b04f549f989283123a29c2",
  3. "Name": "test",
  4. "Policies": {
  5. "PasswordPolicy": {
  6. "MinimumLength": 8,
  7. "RequireUppercase": true,
  8. "RequireLowercase": true,
  9. "RequireNumbers": true,
  10. "RequireSymbols": true,
  11. "TemporaryPasswordValidityDays": 7
  12. }
  13. },
  14. "LastModifiedDate": "2021-10-06T11:57:21.883Z",
  15. "CreationDate": "2021-10-06T11:57:21.883Z",
  16. "SchemaAttributes": [],
  17. "VerificationMessageTemplate": {
  18. "DefaultEmailOption": "CONFIRM_WITH_CODE"
  19. },
  20. "EmailConfiguration": {
  21. "EmailSendingAccount": "COGNITO_DEFAULT"
  22. },
  23. "AdminCreateUserConfig": {
  24. "AllowAdminCreateUserOnly": false
  25. },
  26. "Arn": "arn:aws:cognito-idp:us-east-1:000000000000:userpool/us-east-1_fd924693e9b04f549f989283123a29c2"
  27. }

We will need the user pool’s id for further operations, so save it in a pool_id variable:

  1. $ pool_id=<your-pool-id>

Alternatively, you can also use a JSON processor like jq to directly extract the necessary information when creating a pool in the first place:

  1. $ pool_id=$(awslocal cognito-idp create-user-pool --pool-name test | jq -rc ".UserPool.Id")

Adding a Client

Now we add a client to our newly created pool. Again, we will also need the ID of the created client for the next step. The complete command for client creation with subsequent ID extraction is therefore:

  1. $ client_id=$(awslocal cognito-idp create-user-pool-client --user-pool-id $pool_id --client-name test-client | jq -rc ".UserPoolClient.ClientId")

Signing up and confirming a user

With these steps already taken, we can now sign up a user:

  1. $ awslocal cognito-idp sign-up --client-id $client_id --username example_user --password 12345678 --user-attributes Name=email,Value=<your.email@address.com>

The response should look similar to this:

  1. {
  2. "UserConfirmed": false,
  3. "UserSub": "5fdbe1d5-7901-4fee-9d1d-518103789c94"
  4. }

After the user is created, a confirmation code is generated. The code is printed in the LocalStack container logs (see below), and can optionally also be sent via email if you have SMTP configured.

  1. INFO:localstack_ext.services.cognito.cognito_idp_api: Confirmation code for Cognito user example_user: 125796
  2. DEBUG:localstack_ext.bootstrap.email_utils: Sending confirmation code via email to "your.email@address.com"

We can confirm the user with the activation code, using the following command:

  1. $ awslocal cognito-idp confirm-sign-up --client-id $client_id --username example_user --confirmation-code <received-confirmation-code>

As the above command doesn’t return an answer, we check the pool to see that the request was successful:

  1. $ awslocal cognito-idp list-users --user-pool-id $pool_id
  2. {
  3. "Users": [
  4. {
  5. "Username": "example_user",
  6. "Attributes": [
  7. {
  8. "Name": "email",
  9. "Value": "your.email@address.com"
  10. },
  11. {
  12. "Name": "sub",
  13. "Value": "5fdbe1d5-7901-4fee-9d1d-518103789c94"
  14. },
  15. {
  16. "Name": "cognito:username",
  17. "Value": "example_user"
  18. }
  19. ],
  20. "Enabled": true,
  21. "UserStatus": "CONFIRMED"
  22. }
  23. ]
  24. }

JWT token issuer and JSON Web Key Sets (JWKS) endpoints

The JWT tokens created by Cognito contain an issuer (iss) attribute that represents the endpoint of the corresponding user pool. The issuer endpoint generally follows this pattern, where <pool_id> is the ID of the Cognito user pool:

  1. http://localhost:4566/<pool_id>

Under certain circumstances (depending on your configurations), there may be slight nuances of the issuer URL, like:

  1. https://cognito-idp.localhost.localstack.cloud/<pool_id>

You can access the JSON Web Key Sets (JWKS) configuration under the following standardized well-known URL for each user pool:

  1. $ curl 'http://localhost:4566/<pool_id>/.well-known/jwks.json'
  2. {"keys": [{"kty": "RSA", "alg": "RS256", "use": "sig", "kid": "test-key", "n": "k6lrbEH..."]}

Additionally, the global region-specific public keys for Cognito Identity Pools can be retrieved under this endpoint:

  1. $ curl http://localhost:4566/.well-known/jwks_uri
  2. {"keys": [{"kty": "RSA", "alg": "RS512", "use": "sig", "kid": "ap-northeast-11", "n": "AI7mc1assO5..."]}

Cognito Lambda Triggers

Cognito provides a number of lifecycle hooks in the form of Cognito Lambda triggers. These triggers can be used to react to various lifecycle events and customize the behavior of user signup, confirmation, migration, etc.

For example, to define a user migration Lambda trigger, we can first create a Lambda function (say, named "f1") capable of performing the migration, and then define the corresponding --lambda-config on the user pool creation:

  1. $ awslocal cognito-idp create-user-pool --pool-name test2 --lambda-config '{"UserMigration":"arn:aws:lambda:us-east-1:000000000000:function:f1"}'

Upon authentication of a non-registered user, Cognito will then automatically call the migration Lambda function and finally add the migrated user to the pool.

More details on Cognito Lambda triggers can be found in the AWS documentation.

OAuth Flows via Cognito Login Form

You can also access the local Cognito login form by entering the following URL in your browser:

  1. http://localhost:4566/login?response_type=code&client_id=<client_id>&redirect_uri=<redirect_uri>

Please replace <client_id> with the ID of an existing user pool client ID (in this case, example_user), and <redirect_uri> with the redirect URI of your application (e.g., http://example.com).

The login form should look similar to the screenshot below:

Cognito - 图1

After successful login, the page will redirect to the specified <redirect_uri>, with a path parameter ?code=<code> appended, e.g., http://example.com?code=test123. Obtain a token by submitting that code with grant_type=authorization_code the LocalStack’s implementation of the Cognito OAuth2 TOKEN Endpoint documented here. Note that the value of the redirect_uri parameter must match the value provided during login.

  1. % curl \
  2. --data-urlencode 'grant_type=authorization_code' \
  3. --data-urlencode 'redirect_uri=http://example.com' \
  4. --data-urlencode "client_id=${client_id}" \
  5. --data-urlencode 'code=test123' \
  6. 'http://localhost:4566/oauth2/token'
  7. {"access_token": "eyJ0eXAi…lKaHx44Q", "expires_in": 86400, "token_type": "Bearer", "refresh_token": "e3f08304", "id_token": "eyJ0eXAi…ADTXv5mA"}

Serverless and Cognito

You can also use Cognito and LocalStack in conjunction with the Serverless framework.

For example, take this snippet of a serverless.yml configuration:

  1. service: test
  2. plugins:
  3. - serverless-deployment-bucket
  4. - serverless-pseudo-parameters
  5. - serverless-localstack
  6. custom:
  7. localstack:
  8. stages: [local]
  9. functions:
  10. http_request:
  11. handler: http.request
  12. events:
  13. - http:
  14. path: v1/request
  15. authorizer:
  16. arn: arn:aws:cognito-idp:us-east-1:#{AWS::AccountId}:userpool/ExampleUserPool
  17. resources:
  18. Resources:
  19. UserPool:
  20. Type: AWS::Cognito::UserPool
  21. Properties:
  22. ...

The serverless configuration can then be deployed using serverless deploy --stage local. The example contains a Lambda function http_request which is connected to an API Gateway endpoint. Once deployed, the v1/request API Gateway endpoint will be secured against the Cognito user pool “ExampleUserPool”. You can then register users against that local pool, using the same API calls as for AWS.

In order to make requests against the secured API Gateway endpoint, use the local Cognito API to retrieve identity credentials which can be sent along as Authentication HTTP headers (where test-1234567 is the name of the access key ID generated by Cognito):

  1. Authentication: AWS4-HMAC-SHA256 Credential=test-1234567/20190821/us-east-1/cognito-idp/aws4_request ...

Further reading

For a more detailed example, please check out our sample repository.

Last modified June 1, 2022: update docs on Cognito JWKS well-known endpoints (#176) (00b78c63)