Add authentication to your Single Page Application (SPA) within minutes and take full control of the authentication flow including customizable email templates and a beautiful default UI. See the live demo.
A Terraform module to setup a serverless and easily customizable Authentication as a Service (AaaS) provider in front of API Gateway using AWS Cognito User Pools.
- Authentication using email and password or refresh token
- Registration, password reset and verification
- Completely customizable transactional emails
- Optional multi-part default email templates (see screenshots)
- Optional beautiful and mobile-friendly default UI (see screenshots)
- Federated identities using Cognito Identity Pools and User Pools
- A+ security rating on Mozilla Observatory (CSP, HSTS, etc.)
- Excessively tested with automated unit and acceptance tests
- Serverless, extremely scalable and cost effective
- However, there are some limitations
This module creates a REST API using AWS API Gateway, Lambda and Cognito User Pools to enable registration, authentication and account recovery without the necessity for the implementation of complex OAuth authentication flows. It was originally inspired by LambdAuth but uses User Pools in favor of Identity Pools because exposing (even temporary) AWS credentials is a security threat.
Account registration and recovery circumvent Cognito's default verification logic and emit verification codes to an SNS topic which can be hooked up to a Lambda function handling delivery via SES using default multi-part email templates. This behavior is optional and can be customized by implementing a custom Lambda function handling email delivery. Furthermore, a beautiful and mobile-friendly default UI can be deployed to a custom subdomain within your hosted zone.
AWS Cognito is free for up to 50.000 monthly active users. After that, pricing starts at $ 0,0055 per monthly active user. Additional cost will be attributed to AWS Lambda, API Gateway and CloudFront but it should be very reasonable compared to what AaaS providers like Auth0 charge. While this module does not provide all features offered by other providers, it should be quite sufficient for securing a Single Page Application.
Add the following module to your Terraform configuration and apply it:
module "cognito-auth" {
source = "squidfunk/cognito-auth/aws"
version = "0.4.2"
namespace = "<namespace>"
region = "<region>"
cognito_identity_pool_name = "<pool-name>"
cognito_identity_pool_provider = "<pool-provider>"
# Optional: Default UI
app_hosted_zone_id = "<hosted-zone-id>"
app_certificate_arn = "<certificate-arn>"
app_domain = "<domain>"
app_origin = "<origin-domain>"
# Optional: Email delivery
ses_sender_address = "<email>"
}
All resources are prefixed with the value specified as namespace
. If the S3
bucket name (see below) is not explicitly set, it's set to the given namespace
which means there must not already exist an S3 bucket with the same name. This
is a common source of error.
The cognito_identity_pool_provider
should match the domain name under which
the authentication provider should be deployed, i.e. it should be equal to
app_domain
. Also note that SES is sandboxed by default, so every email address
needs to be verified for delivery. Contact AWS to exit sandboxed mode for
production use.
Also see the example configuration and the live demo.
The following variables can be configured:
- Description: AWS resource namespace/prefix (lowercase alphanumeric)
- Default:
none
- Description: AWS region
- Default:
none
- Description: API deployment stage
- Default:
"production"
- Description: Cognito identity pool name
- Default:
none
- Description: Cognito identity pool provider
- Default:
none
- Description: Application hosted zone identifier
- Implies:
app_certificate_arn
,app_domain
andapp_origin
- Default:
""
- Description: Application domain certificate ARN
- Implies:
app_hosted_zone_id
,app_domain
andapp_origin
- Default:
""
- Description: Application domain
- Implies:
app_hosted_zone_id
,app_certificate_arn
andapp_origin
- Default:
""
- Description: Application origin domain (target domain)
- Implies:
app_hosted_zone_id
,app_certificate_arn
andapp_domain
- Default:
""
- Description: S3 bucket name to store static files
- Default:
"${var.namespace}"
(equal to namespace)
- Description: SES sender email address
- Default:
""
Let's say we want to secure an application hosted on admin.example.com
using
the default UI. First, add the following lines to your Terraform configuration
and apply it:
module "cognito-auth" {
source = "squidfunk/cognito-auth/aws"
version = "0.4.2"
namespace = "example-auth"
region = "us-east-1"
cognito_identity_pool_name = "Example Auth"
cognito_identity_pool_provider = "login.example.com"
# Optional: Default UI
app_hosted_zone_id = "Z*************"
app_certificate_arn = "arn:aws:acm:us-east-1:..."
app_domain = "login.example.com"
app_origin = "admin.example.com"
# Optional: Email delivery
ses_sender_address = "accounts@example.com"
}
Now, when the user visits admin.example.com/dashboard
, the initial API request
should detect a 401 Unauthorized
response for an invalid or expired identity
token and redirect to the default UI:
https://login.example.com/?redirect=dashboard
After successful authentication, the default UI will redirect to the URL
specified in app_origin
appending the path part specified in the redirect
parameter and the identity token in an URI fragment:
https://admin.example.com/dashboard#token=<token>&expires=<timestamp>
Then, after parsing the URI fragment and extracting the token, the application can repeat the request including the identity token as an authorization header:
Authorization: Bearer <token>
If the user checks the Remember me checkbox during the authentication process, a refresh token which is valid for 30 days is issued and sent to the client as a secure HTTP-only cookie. When the access token expires after 1 hour, the client is again redirected to the default UI which will immediately perform a password-less authentication using the refresh token.
To sign out, the application must redirect the user to the following URL:
https://login.example.com/leave
This will invalidate all tokens including the refresh token stored in the secure HTTP-only cookie.
A live demo of this project can be found here. Please note that emails are configured to remain undelivered due to security reasons. If you want to try authentication, you may use the following credentials:
Username: jane.doe@example.com
Password: depl0y&D3STROY
The target domain is localhost:8000
, so you can start a local development
server on that port in order to receive the identity token after a successful
authentication attempt.
By default, AWS Cognito does only allow minor customizations of the whole authentication flow - specifically multi-part emails are not supported; welcome to the 21st century. To work around these restrictions registration and password reset were decoupled using the Cognito Identity Service Provider admin APIs. Verification is implemented with custom verification codes and email delivery. However, as Cognito currently does not support setting the password for a user through the admin API, the user is deleted and recreated with the exact same identifier (a UUID). This is heavily tested with acceptance tests and just works, but it's not ideal. Hopefully AWS will address these issues in the future.
MIT License
Copyright (c) 2018-2019 Martin Donath
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.