Securing APIs with Amazon Cognito
The following guide assumes prior knowledge of Amazon Cognito and JWT based user Authentication and Authorization. We'll discuss how to setup API Gateway level token authentication with Nitric APIs in the cloud, as well as how to create Nitric API middleware to perform basic Authorization of requests.
Prerequisites
- Node.js
- The Nitric CLI
- An account with AWS
- An Amazon Cognito user pool
Creating an API with Nitric
We'll start by creating an example API with Nitric, which we can then secure using Amazon Cognito. To begin, create a new project with the Nitric CLI using the new
command.
nitric new
In this new Nitric project there will be an example API with a single GET: /hello/:name
route. We'll start by securing this route.
Rejecting unauthenticated requests with API Gateways
Nitric APIs allow initial token authentication to be performed by the API Gateways of various cloud providers, such as AWS API Gateway. When configured correctly this will ensure unauthenticated requests are rejected before reaching your application code.
To add this API Gateway authentication we need to create a security definition
and then apply that definition to specific routes or the entire API. Here we'll update the main
API by adding a new security definition named cognito
.
You will need to update the region
, user-pool-id
and app-client-id
values to match your values from Amazon Cognito.
Next, we need to ensure this security definition is applied, otherwise it won't be enforced. APIs can have multiple security definitions applied at different levels or to different routes. For example, you might have one security definition for external users and another for administrators/internal users.
In this example we'll apply the security at the API level, ensuring all routes require authentication.
It's worth noting that these security definitions are not enforced when testing your Nitric services locally. Currently, they're only enforced when the services are deployed to cloud environments.
Authorizing requests with middleware
Now that we have the basic authentication done at the API Gateway we can extract the JWT from the Authorization
header and perform additional checks to authorize requests.
In the example below we're simply checking whether the user is a member of a particular group in our Cognito user pool. If they are the request is allowed through, otherwise it's rejected with an HTTP 401 Unauthorized
status.
This example stores the user information extracted from their authorization token in the request context object, making that data available in subsequent handlers or middleware.
Testing authentication and authorization
Using the AWS CLI we can quickly generate a new user and token, then test our authentication flow to ensure it behaves as expected.
If you already have a test user, you can skip the first two steps. These commands let you create and verify a new user.
# create a new useraws cognito-idp sign-up --region <region> --client-id <app-client-id> --username nitric@example.com --password SuperSafePassword# verify the new user, so they're able to sign-inaws cognito-idp admin-confirm-sign-up --region <region> --user-pool-id <user-pool-id> --username nitric@example.com
Next, you'll need to create a JSON file contain the details you want to use to sign-in.
{"UserPoolId": "<user-pool-id>","ClientId": "<app-client-id>","AuthFlow": "ADMIN_NO_SRP_AUTH","AuthParameters": {"USERNAME": "nitric@example.com","PASSWORD": "SuperSafePassword"}}
Finally, you can sign-in as the user, using this file.
aws cognito-idp admin-initiate-auth --region <regionb> --cli-input-json file://test-auth.json
The output will look something like this:
{"ChallengeParameters": {},"AuthenticationResult": {"AccessToken": "...","ExpiresIn": 3600,"TokenType": "Bearer","RefreshToken": "...","IdToken": "..."}}
You can use the value of the AccessToken
property in the Authorization header of your test requests. Since this is a new user with no group memberships they should initially be rejected. If you then create an "authors" group, add the user to that group and sign-in a second time (generating a new token, which contains the group membership) authorization will subsequently succeed.