Secure Machine-to-Machine OAuth 2.0 Authentication Integration with AWS Cognito, CDK, and API Gateway
In today’s interconnected world, machines often communicate with each other to exchange data. To protect sensitive information and maintain system integrity, it’s crucial to have a secure authentication mechanism for these interactions. In this blog post, we’ll explore how to achieve secure machine-to-machine authentication using AWS Cognito, AWS CDK, and API Gateway with a simple and easy-to-understand approach.
What is AWS Cognito?
AWS Cognito is a fully managed authentication service by Amazon Web Services. It allows us to add user sign-up, sign-in, and access control to our applications. With Cognito, we can easily create and manage user pools, which act as directories for authentication and authorization purposes. It’s a powerful tool to manage user identities and permissions effectively.
Why Use this Mechanism Over IAM or Other M2M Authentication Mechanisms ?
While AWS provides several authentication mechanisms for machine-to-machine (M2M) communication, using AWS Cognito, CDK, and API Gateway offers distinct advantages that make it a preferable choice over other methods like IAM, API keys, mTLS, and EC2 instance profiles.
- Fine-Grained Access Control: AWS Cognito, combined with custom scopes, allows for fine-grained access control. We can define specific permissions for different types of machines, granting them access only to the necessary resources. In contrast, IAM provides broader permissions at the user or role level, which might not be as granular as required for M2M communication.
- Scalability and Flexibility: The integration of AWS Cognito, CDK, and API Gateway offers a scalable and flexible solution for M2M authentication. Managing client credentials within the Cognito User Pool allows us to easily add or revoke machine access, adapting to changing requirements. This dynamic control over access is not as straightforward with IAM or API keys.
- OAuth 2.0 Integration: By incorporating OAuth 2.0 with AWS Cognito, we enable third-party applications to access our resources on behalf of users, enhancing the versatility of our M2M communication. This is particularly valuable when external services need secure access to specific resources while adhering to our authentication policies.
- Centralized User Directory: AWS Cognito acts as a centralized user directory, facilitating efficient user and machine management. This becomes particularly beneficial when multiple applications and services need access to the same user pool. IAM, API keys, and instance profiles lack the user directory capabilities of AWS Cognito, making it less suitable for managing M2M communication across various services.
- Reduced Risk with Client Credentials: The use of unique client credentials (client ID and client secret) in the OAuth 2.0 flow reduces the risk of unauthorized access. Unlike API keys, which can be more static and shared, client credentials are tied to specific machines, and they can be easily rotated to maintain a higher level of security.
In conclusion, the combination of AWS Cognito, CDK, and API Gateway provides a powerful and secure mechanism for machine-to-machine authentication. The solution offers fine-grained access control, scalability, flexibility, and built-in token validation, making it a preferred choice over IAM, API keys, mTLS, and instance profiles. By integrating OAuth 2.0, we enhance the versatility of our M2M communication while maintaining centralized user management and ensuring robust security for resources.
Setting up AWS CDK for Infrastructure as Code
To ensure a repeatable and scalable deployment process, we’ll use AWS CDK as Infrastructure as Code. CDK allows you to define your cloud infrastructure using familiar programming languages like TypeScript, Python, or Java. Let’s quickly go through the setup process:
- Install AWS CDK CLI: Follow the official documentation to install the AWS CDK CLI on your local machine.
- Initialize a New CDK Project: Create a new directory for your project and run below command to initialize a new CDK project. Here we’re using typescript as a programming language.
cdk init app — language=[csharp|fsharp|go|java|javascript|python|typescript]
3. Install Necessary Dependencies: Install the required AWS SDK and any other dependencies using npm or yarn.
Creating an AWS Cognito User Pool with Custom Scopes
To tailor the access permissions for our APIs, we’ll create an AWS Cognito User Pool with custom scopes. Custom scopes allow us to define specific permissions, such as “read” or “write,” for different types of users as per your requirement. By setting up custom scopes, we can easily manage access control for our machine-to-machine communication
Here is sample code to create UserPool and custom scopes. We’ve created two scopes “user.read” and “user.write”
Now, we’ll be attached this above scopes to resource server as below
Implementing Machine to Machine Authentication
Machine to machine communication often involves server-to-server interactions, where authentication is done using credentials specific to the machines rather than user credentials.
- Generate Client Credentials: Within the Cognito User Pool, generate unique client credentials (client ID and client secret) for your machines.
- Use Client Credentials: Securely store these credentials on your machines and use them to obtain access tokens from Cognito for subsequent API requests. You can get access token from this access point and read documentation here that how we can get the access token.
During the setup, you have the option to customize various aspects of the Cognito User Pool client. Ensure that you configure all scopes in oAuth to maintain proper access control.
Additionally, you have the flexibility to choose between using the default Cognito domain or setting up a custom domain for your User Pool. If you opt for a custom domain, you can add a prefix text to the domain URL for branding purposes.
By following these steps and customizing the configuration options as needed, you can establish a secure machine-to-machine communication system using client credentials within the Cognito User Pool.
Note: Remember the domain name should be unique all across AWS accounts.
Implementing API Gateway and Lambda
With AWS Cognito and OAuth 2.0 set up, we’ll implement API Gateway to act as the entry point for our machine-to-machine communication. We’ll create a Lambda function that returns a simple response to validate the authentication process.
Our Lambda is ready to rock and roll. We’ll define our API Gateway (REST API)and resource(user).
We are going to create a new Authorizer within API Gateway, in this case CognitoUserPoolAuthorizer, with an attached UserPool that we generated before.
We have now added the GET method to the user resource in order to retrieve data from our lambda. Along with that, we added the appropriate scope as needed for this method and defined the Cognito authorizer and authorization type.
Deploying the Solution with AWS CDK
With all the components set up, it’s time to deploy the complete solution using AWS CDK. CDK simplifies the deployment process by creating or updating the necessary resources based on the code we defined. This ensures consistency across deployments and reduces the chances of manual errors.
Deploy the Stack: Run cdk deploy
to deploy the entire infrastructure, including AWS Cognito User Pool, API Gateway with Cognito authorizer, and any other necessary resources.
Testing
Now we’re ready to test whole machine-to-machine authentication using AWS Cognito and API Gateway
You need to get your client id and client secret from aws console. It’s very sensitive
To test our machine-to-machine authentication, we’ll first get an access token using client credentials. This token represents the machine’s identity and permissions.
curl — location ‘https://sample-oauth.auth.eu-west-2.amazoncognito.com/oauth2/token' \
— header ‘Content-Type: application/x-www-form-urlencoded’ \
— header ‘Authorization: Basic NuRnbjBjNWFycWYxMXFxbTNzOGtmJnYzZ2NjEzdWtxbGNuYmNqZTdgxoiqjBuc2hhNHRxdWJwJiiXVsdmw2NYQwcDHHmNhOWnmvbIFkcg==’ \
— data-urlencode ‘grant_type=client_credentials’ \
— data-urlencode ‘scope=OauthResourceServer/user.read’
In response, you get access token which is look like this
{
“access_token”: “access_token_value”,
“expires_in”: 3600,
“token_type”: “Bearer”
}
We’ll then use this access token to authenticate our API Gateway requests, ensuring that only authorized machines can access our APIs.
curl — location ‘https://7scdlnkrc5.execute-api.eu-west-2.amazonaws.com/prod/user' \
— header ‘Authorization: access_token_value’
If you get response as we defined in our lambda then your authorisation is granted if not then you received 401(Unauthorised) in the response.
Based on the screenshot provided, to programmatically obtain a token from the oauth2/token endpoint in an actual application, you can use a programming language like typescript and the got
library to make an HTTP POST request with your client credentials (client ID and client secret) to the token endpoint. The response will contain the access token, which you can then use for authenticating API requests. Here's an example:
How to Store the Credentials and How Often They Need to Be Rotated?
Storing machine-to-machine (M2M) credentials securely is crucial to prevent unauthorized access and ensure the integrity of our system. The client credentials, consisting of the client ID and client secret, are used for obtaining access tokens from AWS Cognito. Here’s how we can store and manage these credentials securely:
- Use Secure Storage Services: Consider using AWS Secrets Manager or other secure credential management services provided by cloud platforms. These services offer encryption and access controls to safeguard sensitive information.
- Credential Rotation Policy: Implement a credential rotation policy to enhance security. Regularly rotate the client secret at predefined intervals. The frequency of rotation depends on your organization’s security requirements and the sensitivity of the data being accessed. A common practice is to rotate credentials every 90 days or as mandated by your security policies.
- Secure Communication: Ensure secure communication between machines and AWS Cognito during credential exchange. Use encrypted channels (e.g., HTTPS) when requesting access tokens to prevent eavesdropping or man-in-the-middle attacks.
- Limited Access to Credentials: Limit access to the credentials to authorized personnel only. Follow the principle of least privilege, granting access only to those who need it for administrative or maintenance tasks.
Remember that the security of your machine-to-machine communication heavily relies on how well you protect and manage the client credentials. Regularly assess and update your security practices to align with industry best practices and evolving security threats. By employing a robust credential management strategy, you can ensure a secure and reliable M2M authentication mechanism.
Conclusion
In this blog post, we explored how to implement secure machine-to-machine authentication using AWS Cognito, AWS CDK, and API Gateway. By creating an AWS Cognito User Pool with custom scopes, and leveraging AWS CDK for infrastructure as code, we built a robust and scalable authentication mechanism for our machine-to-machine communication. Secure authentication is crucial for protecting sensitive data and ensuring the integrity of our systems in today’s interconnected world.
You can check my Github repository for more information.
Cheers
Happy Coding :)