Secure your API resources for machine-to-machine communication
Learn how to leverage OAuth 2.0 and JWT to secure your API resources for machine-to-machine communication.
When building a project that involves multiple services, the security of the API resources is a critical concern. In this article, I'll show you how to leverage OAuth 2.0 and JWT to secure the communication between services (machine-to-machine), and how to apply role-based access control (RBAC) to follow the minimum privilege principle.
Get started
To follow along, I assume you have the following prerequisites:
- A Logto Cloud account, or a self-hosted Logto instance
- At least two services that need to communicate with each other
For demonstration purposes, let's assume we have the following services:
- A shopping cart service that provides APIs to manage shopping carts
- Endpoint:
https://cart.example.com/api
- Endpoint:
- A payment service that provides APIs to process payments
- Endpoint:
https://payment.example.com/api
- Endpoint:
Authentication flow
Now, our cart service needs to call the payment service to process payments. The authentication flow is as follows:
Some key concepts in the above diagram:
- JWT (RFC 7519): JSON Web Token. See our previous article for an introduction to JWT.
- JWK (RFC 7517): JSON Web Key which is used to verify the signature of a JWT. A JWK set is a set of JWKs.
- "client_credentials" grant (RFC 6749): A grant type in OAuth 2.0. It uses the client's credentials to obtain an access token. We'll demonstrate the details in the upcoming sections.
Each participant in the above diagram has a role to play in the authentication flow:
- Cart service: The client that needs to call the payment service. Although it's a service, it's still a client in the OAuth 2.0 context, and we call such clients "machine-to-machine applications" in Logto.
- Logto: The OAuth 2.0 authorization server that issues access tokens.
- Payment service: The API resource that provides APIs to process payments.
Let's go through the authentication flow step by step.
Initial setup
To perform the authentication flow, we need to create a machine-to-machine app (cart service) and an API resource (payment service) in Logto.
Create API resource
Since our cart service needs to be aware of the payment service's API when performing authentication, we need to create an API resource first. Go to Logto Console, click API resources in the left sidebar, and click Create API resource. In the opened dialog, we offer some tutorials to help you get started. You can also click Continue without tutorial to skip it.
Enter the API name and identifier, for example, Payment service
and https://payment.example.com/api
, then click Create API resource.
After creating the API resource, you'll be redirected to the details page. We can leave it as is for now.
Create machine-to-machine app
Click Applications in the left sidebar, and click Create application. In the opened dialog, find the Machine-to-machine card, then click Start building.
Enter the application name, for example, Cart service
, and click Create application. An interactive guide will be shown to help you set up the application. You can follow the guide to understand the basic usage, or click Finish and done to skip it.
Request access token
Since machine-to-machine applications are assumed to be secure (e.g., they are deployed in a private network), we can use the OAuth 2.0 "client_credentials" grant to obtain an access token. It uses basic authentication to authenticate the client:
- The request URL is the token endpoint of your Logto instance. You can find and copy it in the Advanced settings tab of the machine-to-machine app details page.
- The request method is
POST
. - The request
Content-Type
header isapplication/x-www-form-urlencoded
. - For the
Authorization
header, the value isBasic <base64(app_id:app_secret)>
, whereapp_id
andapp_secret
are the app ID and app secret of the machine-to-machine app respectively. You can find them in the application details page. - The request body needs to specify the grant type and the API identifier. For example,
grant_type=client_credentials&resource=https://payment.example.com/api
.grant_type=client_credentials
: The constant value for the "client_credentials" grant.resource=https://payment.example.com/api
: The API identifier of the API resource that the client wants to access.- If the application needs to be authorized with scopes (permissions), you can also specify the scopes in the request body. For example,
scope=read:payment write:payment
. We'll cover scopes later.
Here's an example of the request using curl
:
A successful response body would be like:
Send request with authorization header
Now we have the access token, and we can append it to the Authorization
header of the request to the API resource. For example, if we want to call the POST /payments
API of the payment service, we can send the following request:
Validate JWT
You may notice that the payment service needs to validate the JWT using the JWK set, and may have a local JWK set cache to avoid fetching the JWK set from Logto every time. Fortunately, due to the popularity of JWT, there are many libraries that can help you to achieve the goal with several lines of code.
These libraries are usually called "jose" (JavaScript Object Signing and Encryption) or "jsonwebtoken". For instance, in Node.js we can use jose to validate the JWT:
If the validation succeeds, the payload
variable will be the decoded JWT payload. Otherwise, an error will be thrown.
Apply role-based access control
Now we have successfully secured the communication between the cart service and the payment service. However, the authentication flow only ensures that the client is the real cart service, but doesn't ensure that the cart service has any permission to perform actions on the payment service.
Let's say we want to allow the cart service to create payments, but not to read payments.
Define permissions
In Logto, "scopes" and "permissions" are interchangeable. Go to the API resource details page of the payment service, and navigate to the Permissions tab. It should be empty now. Click Create permission, enter read:payment
as the permission name, and enter Read payments
as the permission description. Then click Create permission.
Repeat the above steps to create another permission with the name write:payment
and the description Create payments
.
Create machine-to-machine role
A role is a group of permissions. In Logto, machine-to-machine apps can be assigned roles to grant permissions. Click "Roles" in the left sidebar, and click Create role.
- Enter
checkout
as the role name, and enterCheckout service
as the role description. - Click Show more options. Choose "Machine-to-machine app role" as the role type.
- In the "Assigned permissions" section, click the arrow icon on the left of the API resource name (Payment service), and select the
write:payment
permission. - Click Create role.
- Since we already have a machine-to-machine app (Cart service), we can directly assign the role to it in the next step. Check the checkbox on the left of the app name (Cart service), and click Assign applications.
Request access token with scopes
In addition to the request body parameters we mentioned in Request access token, we can also specify the scopes in the request body. For example, if we want to request the write:payment
permission, we can send the following request:
To request multiple scopes, you can separate them with spaces. For example, scope=write:payment read:payment
.
Validate scopes
If an action needs the write:payment
permission in the payment service, we can validate the scopes by asserting that the scope
claim of the JWT payload:
Conclusion
If you want to protect the access to the cart service, you can also apply the same authentication flow. This time, the cart service is the API resource, and the client is another service that needs to access.
With Logto, your API resources are secured with OAuth 2.0 and JWT, and the you can follow the minimum privilege principle by applying role-based access control. Besides, you can also use Logto to manage your users and their permissions, and even integrate with third-party identity providers.