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.
-
Installation
Section titled “Installation”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-pythonAlternatively, you can use the REST APIs directly.
-
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/authenticationcurl -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..."}from scalekit.v1.clients.clients_pb2 import OrganizationClientorg_id = "<SCALEKIT_ORGANIZATION_ID>"api_client = OrganizationClient(name="GitHub Actions Deployment Service", # A descriptive name for the API clientdescription="Service account for GitHub Actions to deploy applications to production", # A detailed explanation of the client's purpose and usagecustom_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=["deploy:applications", "read:deployments"], # List of permissions the client needsaudience=["deployment-api.acmecorp.com"], # List of API endpoints this client will accessexpiry=3600 # Token expiration time in seconds. Defaults to 3600 (1 hour))response = scalekit_client.m2m_client.create_organization_client(organization_id=org_id,m2m_client=api_client)# Persist the generated credentials securely in your applicationclient_id = response.client.client_idplain_secret = response.plain_secretSample 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_ly8G57h0ErRJSObJI6dShkoaq6bigo11Dxcf.."} -
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_idandclient_secretissued 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 theaccess_token.The client sends a POST request to the
/oauth/tokenendpoint: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>" \client_id = "API_CLIENT_ID"client_secret = "API_CLIENT_SECRET"token_response = scalekit_client.generate_client_token(client_id=client_id,client_secret=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
Authorizationheader of subsequent requests to your API server. Your API server validates these tokens before granting access to resources. -
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:
-
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());});});}# This is automatically handled by Scalekit SDK -
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');}}# Token from the incoming API request's authorization headertoken = token_response["<API_CLIENT_JWT_ACCESS_TOKEN>"]claims = scalekit_client.validate_access_token_and_get_claims(token=token)
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.
-