Skip to content

Add auth to your APIs

Secure your APIs in minutes with OAuth 2.0 client credentials, scoped access, and JWT validation using Scalekit

APIs are a fundamental mechanism for systems to communicate and exchange data. By exposing API endpoints, applications enable customers, partners, and external systems to interact seamlessly, creating deeper integrations and increasing the stickiness to your app and it’s usecases.

Exposing APIs without any authentication is like leaving your door wide open for anyone to walk in. Anyone can access your API endpoints, potentially causing security breaches, data leaks, and unauthorized usage.

In this guide let’s take a look at how your can add OAuth2.0 based client credentials authentication to your APIs to ensure only authorized clients can consume your APIs.

API ClientUserYour AppScalekit 1. Request to register an API client with necessary scopes 2. Creates an API client in your Scalekit environment 3. Gives you a client_id and client_secret Show the client credentials 4. Saves the API credetials 5. Requests access to protected resource 6. Validates the access tokens 7. Returns the protected resource
  1. Scalekit becomes the authorization server for your APIs. Using Scalekit provides necessary methods to register and authenticate API clients.

    Terminal window
    pip install scalekit-sdk-python

    Alternatively, you can use the REST APIs directly.

  2. Enable API client registration for your customers

    Section titled “Enable API client registration for your customers”

    To enable secure API access, you must first register an application as an API client. This process generates unique credentials that will authenticate the client when interacting with your API.

    Upon registration, your app obtains a client ID and secret — essentially an application-level authentication mechanism. These credentials should be securely shared with your API client developers for integration (usually via view-once UI).

    POST /organizations/{organization_id}/clients
    # For authentication details, see: http://docs.scalekit.com/apis#description/authentication
    curl -L '<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", # A descriptive name for the API client
    "description": "Service account for GitHub Actions to deploy applications to production", # A detailed explanation of the clients purpose and usage
    "custom_claims": [ # Key-value pairs that provide additional context about the client. Each claim must have a `key` and `value` field
    {
    "key": "github_repository",
    "value": "acmecorp/inventory-service"
    },
    {
    "key": "environment",
    "value": "production_us"
    }
    ],
    "scopes": [ # List of permissions the client needs (e.g., ["deploy:applications", "read:deployments"])
    "deploy:applications",
    "read:deployments"
    ],
    "audience": [ # List of API endpoints this client will access (e.g., ["deployment-api.acmecorp.com"])
    "deployment-api.acmecorp.com"
    ],
    "expiry": 3600 # Token expiration time in seconds. Defaults to 3600 (1 hour)
    }'
    Sample 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_ly8G57h0ErRJSObJI6dShkoa..."
    }
  3. API client requests Bearer access token for API authentication

    Section titled “API client requests Bearer access token for API authentication”

    API clients use the client_id and client_secret issued in the previous step to reach your Scalekit environment and get the access token. No action is needed by you in your API server. This section only demonstrates how API clients get the access_token.

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

    POST /oauth/token
    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=<API_CLIENT_ID>" \
    -d "client_secret=<API_CLIENT_SECRET>" \

    Upon successful authentication, your Scalekit environment issues a JWT access token to the API client.

    Access token response
    {
    "access_token":"<API_CLIENT_JWT_ACCESS_TOKEN>",
    "token_type":"Bearer",
    "expires_in":86399,
    // Same scopes that were granted during client registration
    "scope":"deploy:applications read:deployments"
    }

    The client includes this access token in the Authorization header of subsequent requests to your API server. Your API server validates these tokens before granting access to resources.

  4. Validate and authenticate API client’s access tokens

    Section titled “Validate and authenticate API client’s access tokens”

    Your API server must validate the incoming JWT access token to ensure the request originates from a trusted API 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.

      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());
      });
      });
      }
    2. 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).

      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');
      }
      }

    Upon successful token verification, your API server gains confidence in the request’s legitimacy and can proceed to process the request, leveraging the authorization scopes embedded within the token.