This is a sample Node.js application demonstrating Machine-to-Machine (M2M) authentication using Descope's client credentials flow. It allows you to exchange an access key for a JWT and call protected APIs.
-
Clone the repository:
git clone <your-repo-url> cd node-m2m-sample
-
Install dependencies:
npm install
-
Configure environment variables: Create a
.env
file in the project root with the following variables:DESCOPE_PROJECT_ID=your_descope_project_id DESCOPE_ACCESS_KEY=your_descope_management_access_key PORT=3000 # Optional, defaults to 3000
DESCOPE_PROJECT_ID
: Your Descope project ID (from the Descope console)DESCOPE_ACCESS_KEY
: Your Descope management access keyPORT
: (Optional) Port to run the server on
-
Start the app:
npm start # or for development with auto-reload npm run dev
-
Open in your browser: Visit http://localhost:3000
- Enter your Project ID and Access Key in the UI.
- Select or enter the scopes you want to request.
- Click Get M2M Token to exchange your access key for a JWT.
- View the raw and decoded JWT, expiry countdown, and important claims.
- Use the Try Protected API button to call a protected endpoint with your JWT.
Description: Exchange a Descope access key for a JWT using the M2M (client credentials) flow.
Request Body:
{
"projectId": "string (optional, overrides env)",
"accessKey": "string (optional, overrides env)",
"scopes": ["scope1", "scope2", ...] // optional
}
Response:
200 OK
(success)
{
"success": true,
"jwt": "<JWT string>",
"decodedPayload": { ... },
"decodedHeader": { ... }
}
401 Unauthorized
(error)
{
"success": false,
"error": "Authentication failed"
}
Description: Protected endpoint. Verifies the JWT and returns its claims.
Headers:
Authorization: Bearer <JWT>
Response:
200 OK
(success)
{
"success": true,
"message": "Protected API call succeeded!",
"claims": { ... }
}
401 Unauthorized
(error)
{
"success": false,
"error": "JWT verification failed: ..."
}
Description: Protected endpoint. Requires the JWT to have the read:data
scope.
Headers:
Authorization: Bearer <JWT>
Response:
200 OK
(success)
{
"success": true,
"message": "User API call succeeded!",
"claims": { ... }
}
403 Forbidden
(missing scope)
{
"success": false,
"error": "Missing required scope: 'read:data'"
}
Description: Protected endpoint. Requires the JWT to have the admin
scope.
Headers:
Authorization: Bearer <JWT>
Response:
200 OK
(success)
{
"success": true,
"message": "Admin API call succeeded!",
"claims": { ... }
}
403 Forbidden
(missing scope)
{
"success": false,
"error": "Missing required scope: 'admin'"
}
- The access key is never sent to or stored on the server except for the duration of the request.
- This app is for demo purposes. For production, see security best practices in the code comments and Descope documentation.
- The
/m2m/token
endpoint is specifically for M2M (machine-to-machine) authentication and follows best practices for naming and usage.
To use scopes such as read:data
, write:data
, or to enforce role-based access like admin
or user
with your access keys and JWTs, you must define the following in your Descope project:
- Roles:
admin
,user
- Permissions:
read:data
,write:data
These roles and permissions are attached to your access keys and will be reflected in the JWT claims, allowing your application to enforce authorization based on scopes and roles.
- Log in to the Descope Console.
- Navigate to Settings > Roles & Permissions.
- Click Add Role (the + button).
- Create the following roles:
admin
user
- Optionally, add a description for each role.
- Save the roles.
- In the same Roles & Permissions section, define the following permissions:
read:data
write:data
- Assign these permissions to the roles as appropriate. For example:
- The
admin
role can have bothread:data
andwrite:data
permissions. - The
user
role can have onlyread:data
permission, or both, depending on your needs.
- The
- Go to Access Keys in the Descope Console.
- Create a new access key or edit an existing one.
- In the access key configuration, assign one or more roles (
admin
oruser
) to the key. You can also associate tenants if needed. - Save the access key. Copy the key value for use in your app.
- When you exchange the access key for a JWT (using
/m2m/token
), the roles and permissions you assigned will be included in the JWT claims (e.g., asscope
,scopes
,role
, orroles
). - Your backend can then enforce access control by checking for the required scopes/roles in the JWT claims.
If your API requires the admin
role, ensure the JWT contains admin
in its role
or roles
claim. If your API requires the read:data
permission, ensure the JWT contains read:data
in its scope
or scopes
claim. The sample app already demonstrates this in the /api/admin
and /api/user
endpoints.
For more details, see:
- Descope Docs: Roles & Permissions
- Descope Docs: M2M Access Keys
- Descope Docs: Security Best Practices