Third-Party Authentication and Authorization
Another way to secure APIs is to use a third-party service such as Amazon Cognito and Auth0 with the help of an API gateway. Consider externalizing the authentication and authorization if you already have an existing auth server or you need to scale it independently. Moreover, most third-party services implement federated identity through various protocols such as OpenID Connect and support integration with social identity providers.
For this tutorial, we will use Amazon Cognito as our auth server and Amazon API Gateway as the entry point of our API. The gateway will be responsible for calling the auth server to check if a user is authorized to call the API.
Prerequisites
- Amazon Cognito User Pool. Check out Getting Started with User Pools for a guide on how to create a user pool.
- A REST API. Check out Quick start tutorial: Publishing a RESTful API for a guide on how to create and expose a Gloop REST API.
Creating and adding a user to Amazon Cognito User Pool
First, we need to create and add a user to our Cognito pool. This user will be used to access our API later on.
- Go to Amazon Cognito console and click Manage User Pools > (Your pool name).
- Under General Settings, click Users and groups.
- Click the Create user button and fill out the form. Be sure to remember the username and password.
Creating an application client
An application client is an entity that is allowed to perform API calls to Cognito. In OAuth 2.0 context, this is the application requesting access to the API on behalf of the Cognito user.
- Click App clients under General Settings.
- Click Add an app client.
- Enter the name of the app client. Configure other settings as necessary.
- Click Create app client. Take note of the generated App client ID.
- Under App integration > Domain name, specify a domain name. This is to make the Cognito's Hosted UI available which we will use later on.
Forwarding requests to the REST API
Now that Cognito is configured, we need to tell Amazon API gateway to forward requests to our API. Amazon API Gateway should be able to access our API for this to work correctly. For example, if your Martini instance is hosted locally, you can expose it to the internet by using a tunneling service such as ngrok. For production environment, it is recommended to only expose your APIs on a private network and make sure they are only accessible through the API gateway.
- Go to Amazon API Gateway Console.
- Create an HTTP API by clicking the Build button.
- Click Add integration and select HTTP.
- Paste the URL of the API on the URL endpoint.
- Specify an API name, click Review and Create, then Create.
- Test the API by invoking the Invoke URL with the path of the API.
Integrating Amazon Cognito with Amazon API Gateway
Configure the Amazon API Gateway to contact Amazon Cognito for authorization to prevent unauthorized users from calling the API.
- Click Authorization under the Develop tab.
- Click Manage Authorizers.
- Click Create.
- Use JWT as the Authorizer type.
- Specify a name of the Authorizer.
- Enter the issuer URL of your Amazon Cognito instance. The issuer URL of Cognito has the
format
https://cognito-idp.{region}.amazonaws.com/{userPoolId}
. The user pool ID can be retrieved on the General Settings of Amazon Cognito console. - Add the app client ID as an audience and click Create.
- Click Attach authorizers to routes.
- Click the route and select the authorizer from the list and click Attach authorizer.
To test, hit the API again and you should get a 401: Unauthorized response with body:
1 |
|
Authorizing a request
To hit the API successfully, we need to retrieve a valid access token from Cognito and pass this when invoking the API. To do this, we need to initiate an OAuth flow. There are multiple kinds of OAuth flows. The correct flow to use depends on the type of your application client. Auth0 provides a guide on which flow to use. For this tutorial, we will use the code flow and initiate it using OAuth Tools.
Configuring OAuth Tools
- In OAuth Tools, create an environment by selecting Environments and clicking New environment.
- Input the issuer URL of your Cognito instance then click Discover.
- Under the Client tab, click New client. Specify the application client ID and client secret. You can get these details on Cognito's App clients.
- Enable the Code Flow then exit the dialog.
Getting an access token
-
Initiate a code flow by clicking Start Flow and select Code Flow.
-
Change the environment to the newly created environment.
-
Allow Cognito to callback to OAuth Tools by adding the OAuth Tools' callback URL to Cognito's App client settings.
-
Enable Cognito User Pool as an identity provider, allow the Authorization code grant and
openid
scope then click Save changes. -
In Settings section of OAuth Tools, select the app client, and add
openid
scope. -
Click the Run button then you should be redirected to Cognito's hosted UI.
-
Input the credentials of the Cognito user that was created earlier then click Sign In. If this is the first time the user has signed in, you may need to change its password. Cognito will then redirect you back to OAuth Tools.
-
In OAuth Tools, scroll down and click the Redeem code button. Copy the generated access token.
Performing an authorized request
- Using an HTTP client such as Martini Desktop's HTTP Client, create a new request and enter the URL of the API.
- Under the Authentication tab, set the Type to OAuth 2.0.
- Paste the access token and click Send.
If you are using a different HTTP client, add a header with name Authorization
and set its value
to Bearer {access_token}
instead.
Retrieving User's Identity
In most cases, you may want to persist the user's identity to the API call. In fact, it is recommended to do so to maintain an audit trail. This audit trail can be useful when tracking API requests.
If your authorization server provides access tokens in signed JWT format, the user identity is usually embedded
on the token itself. If the server supports OpenID Connect, it is contained on the ID token instead. This ID token is
also sent after doing an OAuth flow. Depending on the auth server and gateway, you can pass either of these to
the Authorization
bearer header. Martini will then automatically decode the token and makes the user details available
through the IdentityMethods.getCurrentUser()
function. Since most API gateways automatically include
the token to the header, you can immediately use the function to retrieve the user's identity.
If your authorization server provides access tokens in encrypted JWT format, you need to configure a service on the
API gateway to decrypt it then pass the decrypted token to the Authorization
header when calling the API on Martini.
Another option is to configure Martini to get the encrypted token from the header and decrypt it. Decryption is usually
done by a client library which can be imported as a JAR.