Skip to main content

M2M quickstart guide

This guide explains how to implement secure machine-to-machine (M2M) authentication using Scalekit. You will learn how to register a third-party client, authenticate its API requests using client credentials, and validate the received access tokens in your API server.

This guide provides code samples to help you integrate Scalekit's M2M authentication into your application.

Prerequisites

Before starting, ensure you have the following:

This guide does not cover installing or configuring Scalekit itself.

Register an M2M client

To allow an external system (an API consumer) to securely access your API, you first register it as an M2M client within the relevant Scalekit Organization. This step typically occurs when your customer sets up an integration via their dashboard or a setup process you provide.

Register a new M2M client by making a POST request to the /organizations/{organization_id}/clients endpoint.

Register an M2M client
curl -L 'https://<SCALEKIT_ENVIRONMENT_URL>/api/v1/organizations/<ORGANIZATION_ID>/clients' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer <SCALEKIT_ACCESS_TOKEN>' \
-d '{
    "name": "GitHub Actions Deployment Service",
    "description": "Service account for GitHub Actions to deploy applications to production",
    "custom_claims": [
        {
            "key": "github_repository",
            "value": "acmecorp/inventory-service"
        },
        {
            "key": "environment",
            "value": "production_us"
        }
    ],
    "scopes": [
        "deploy:applications",
        "read:deployments"
    ],
    "audience": [
        "deployment-api.acmecorp.com"
    ],
    "expiry": 3600
}'
ParameterTypeRequiredDescription
namestringOptionalA descriptive name for the M2M client (e.g., "GitHub Actions Deployment Service")
descriptionstringOptionalA detailed explanation of the client's purpose and usage
custom_claimsarrayOptionalKey-value pairs that provide additional context about the client. Each claim must have a key and value field
scopesarrayOptionalList of permissions the client needs (e.g., ["deploy:applications", "read:deployments"])
audiencearrayOptionalList of API endpoints this client will access (e.g., ["deployment-api.acmecorp.com"])
expirynumberOptionalToken expiration time in seconds. Defaults to 3600 (1 hour)

The API response contains the client details, including the client_id and client_secret.

Register an M2M client API response
{
    "client": {
        "client_id": "m2morg_68315758685323697",
        "secrets": [
            {
                "id": "sks_68315758802764209",
                "create_time": "2025-04-16T06:56:05.360Z",
                "update_time": "2025-04-16T06:56:05.367190455Z",
                "secret_suffix": "UZ0X",
                "status": "ACTIVE",
                "last_used_time": "2025-04-16T06:56:05.360Z"
            }
        ],
        "name": "GitHub Actions Deployment Service",
        "description": "Service account for GitHub Actions to deploy applications to production",
        "organization_id": "org_59615193906282635",
        "create_time": "2025-04-16T06:56:05.290Z",
        "update_time": "2025-04-16T06:56:05.292145150Z",
        "scopes": [
            "deploy:applications",
            "read:deployments"
        ],
        "audience": [
            "deployment-api.acmecorp.com"
        ],
        "custom_claims": [
            {
                "key": "github_repository",
                "value": "acmecorp/inventory-service"
            },
            {
                "key": "environment",
                "value": "production_us"
            }
        ]
    },
    "plain_secret": "test_ly8G57h0ErRJSObJI6dShkoaq6bigo11Dxcfa6reKG1kKNVbqBKW4H5Ctmb5UZ0X"
}

Note: Scalekit only returns the plain_secret once during client creation and does not store it. Instruct your M2M client developers to store the plain_secret securely.

Authenticate the M2M client

After registration, the M2M client authenticates with your Scalekit environment using its client_id and client_secret (the plain_secret obtained earlier) to request an access token. This process uses the OAuth 2.0 client credentials grant type.

The client sends a POST request to the /oauth/token endpoint:

Authenticate the M2M client
curl -X POST \
  "https://<SCALEKIT_ENVIRONMENT_URL>/oauth/token" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=client_credentials" \
  -d "client_id=<M2M_CLIENT_ID>" \
  -d "client_secret=<M2M_CLIENT_SECRET>" \

Refer to API Authentication for M2M clients for detailed information on how M2M clients authenticate.

Upon successful authentication, Scalekit issues a JWT access token.

Access token response
{
  "access_token":"<M2M_JWT_ACCESS_TOKEN>",
  "token_type":"Bearer",
  "expires_in":86399,
  "scope":"deploy:applications read:deployments"
}

The client includes this access token in the Authorization header of subsequent requests to your API server.

Validate access tokens in your API server

Your API server must validate the incoming JWT access token to ensure the request originates from a trusted M2M client and that the token is legitimate.

Validate the token in two steps:

  1. Retrieve the Public Key: Fetch the appropriate public key from your Scalekit environment's JSON Web Key Set (JWKS) endpoint. Use the kid (Key ID) from the JWT header to identify the correct key. Cache the key according to standard JWKS practices.
Retrieve the public key
import jwksClient from 'jwks-rsa';

const client = jwksClient({
  jwksUri: 'YOUR_JWKS_URI',
  cache: true
});

async function getPublicKey(header: any): Promise<string> {
  return new Promise((resolve, reject) => {
    client.getSigningKey(header.kid, (err, key) => {
      if (err) reject(err);
      else resolve(key.getPublicKey());
    });
  });
}
  1. Verify the Token Signature: Use the retrieved public key and a JWT library to verify the token's signature and claims (like issuer, audience, and expiration).
Verify the token signature
import jwt from 'jsonwebtoken';

async function verifyToken(token: string, publicKey: string) {
  try {
    const decoded = jwt.decode(token, { complete: true });
    const verified = jwt.verify(token, publicKey, {
      algorithms: ['RS256'],
      complete: true
    });
    return verified.payload;
  } catch (error) {
    throw new Error('Token verification failed');
  }
}

Find complete code examples in the Scalekit M2M Gists repository.

If the token verification succeeds, your API server can trust the request's authenticity and proceed with processing it based on the permissions (scopes) encoded within the token.


Is this page helpful? Yes No