Skip to content

Validate API client scopes

Define permissions for API client applications using scopes and validate them in your API server

Scopes define which permissions an API client application can request when accessing your APIs. When you register an API client for your customers, you assign scopes that determine what actions the client app can perform. Scalekit includes these scopes in the access token when the client app authenticates.

This guide shows you how to register API clients with specific scopes for your customers and how to validate those scopes in your API server.

  1. When you enable API client registration for your customers, you define the scopes that their applications can request. For example, you might create an API client for a customer’s deployment service with scopes like deploy:applications and read:deployments.

    Register an API client with specific scopes
    // Use case: Your customer requests API access for their deployment automation.
    // You register an API client app with the appropriate scopes.
    import { ScalekitClient } from '@scalekit-sdk/node';
    // Initialize Scalekit client (see installation guide for setup)
    const scalekit = new ScalekitClient(
    process.env.SCALEKIT_ENVIRONMENT_URL,
    process.env.SCALEKIT_CLIENT_ID,
    process.env.SCALEKIT_CLIENT_SECRET
    );
    async function createAPIClient() {
    try {
    // Define API client details with scopes your customer's app needs
    const clientDetails = {
    name: 'GitHub Actions Deployment Service',
    description: 'Service account for GitHub Actions to deploy applications to production',
    scopes: ['deploy:applications', 'read:deployments'],
    expiry: 3600, // Token expiry in seconds
    };
    // API call to register the client
    const response = await scalekit.m2m.createClient({
    organizationId: process.env.SCALEKIT_ORGANIZATION_ID,
    client: clientDetails,
    });
    // Response contains client details and the plain_secret (only returned once)
    const clientId = response.client.client_id;
    const plainSecret = response.plain_secret;
    // Provide these credentials to your customer securely
    console.log('Created API client:', clientId);
    } catch (error) {
    console.error('Error creating API client:', error);
    }
    }
    createAPIClient();
  2. When your API server receives a request from an API client app, you must validate the scopes present in the access token provided in the Authorization header. The access token is a JSON Web Token (JWT).

    First, let’s look at the claims inside a decoded JWT payload. Scalekit encodes the granted scopes in the scopes field.

    Example decoded access token
    {
    "client_id": "m2morg_69038819013296423",
    "exp": 1745305340,
    "iat": 1745218940,
    "iss": "<SCALEKIT_ENVIRONMENT_URL>",
    "jti": "tkn_69041163914445100",
    "nbf": 1745218940,
    "oid": "org_59615193906282635",
    "scopes": [
    "deploy:applications",
    "read:deployments"
    ],
    "sub": "m2morg_69038819013296423"
    }

    Your API server should inspect the scopes array in the token payload to authorize the requested operation. Here’s how you validate the token and check for a specific scope in your API server.

    Example Express.js middleware for scope validation
    // Security: ALWAYS validate the access token on your server before trusting its claims.
    // This prevents token forgery and ensures the token has not expired.
    import { ScalekitClient } from '@scalekit-sdk/node';
    import jwt from 'jsonwebtoken';
    import jwksClient from 'jwks-rsa';
    const scalekit = new ScalekitClient(
    process.env.SCALEKIT_ENVIRONMENT_URL,
    process.env.SCALEKIT_CLIENT_ID,
    process.env.SCALEKIT_CLIENT_SECRET
    );
    // Setup JWKS client for token verification
    const client = jwksClient({
    jwksUri: `${process.env.SCALEKIT_ENVIRONMENT_URL}/.well-known/jwks.json`,
    cache: true
    });
    async function getPublicKey(header) {
    return new Promise((resolve, reject) => {
    client.getSigningKey(header.kid, (err, key) => {
    if (err) reject(err);
    else resolve(key.getPublicKey());
    });
    });
    }
    async function checkPermissions(req, res, next) {
    const authHeader = req.headers.authorization;
    if (!authHeader || !authHeader.startsWith('Bearer ')) {
    return res.status(401).send('Unauthorized: Missing token');
    }
    const token = authHeader.split(' ')[1];
    try {
    // Decode to get the header with kid
    const decoded = jwt.decode(token, { complete: true });
    const publicKey = await getPublicKey(decoded.header);
    // Verify the token signature and claims
    const verified = jwt.verify(token, publicKey, {
    algorithms: ['RS256'],
    complete: true
    });
    const decodedToken = verified.payload;
    // Check if the API client app has the required scope
    const requiredScope = 'deploy:applications';
    if (decodedToken.scopes && decodedToken.scopes.includes(requiredScope)) {
    // API client app has the required scope, proceed with the request
    next();
    } else {
    // API client app does not have the required scope
    res.status(403).send('Forbidden: Insufficient permissions');
    }
    } catch (error) {
    // Token is invalid or expired
    res.status(401).send('Unauthorized: Invalid token');
    }
    }