adding-oauth2-to-apis
Adding OAuth 2.0 to APIs (Scalekit)
Flow overview
Register client (your app) → Issue client_id + secret (Scalekit) →
API client fetches bearer token → Your server validates JWT + scopes
Security-critical steps (token validation, scope enforcement) use low freedom — follow them exactly.
1. Install
pip install scalekit-sdk-python
# or
npm install @scalekit-sdk/node
Initialize once and reuse:
from scalekit import ScalekitClient
import os
scalekit_client = ScalekitClient(
env_url=os.getenv("SCALEKIT_ENVIRONMENT_URL"),
client_id=os.getenv("SCALEKIT_CLIENT_ID"),
client_secret=os.getenv("SCALEKIT_CLIENT_SECRET")
)
Required env vars: SCALEKIT_ENVIRONMENT_URL, SCALEKIT_CLIENT_ID, SCALEKIT_CLIENT_SECRET.
2. Register an API client for an organization
One organization can have multiple API clients. Registration returns client_id and plain_secret — plain_secret is shown only once; never stored by Scalekit.
from scalekit.v1.clients.clients_pb2 import OrganizationClient
response = scalekit_client.m2m_client.create_organization_client(
organization_id="<ORG_ID>",
m2m_client=OrganizationClient(
name="GitHub Actions Deployment Service",
description="Deploys to production via GitHub Actions",
scopes=["deploy:applications", "read:deployments"], # resource:action pattern
audience=["deployment-api.acmecorp.com"],
custom_claims=[
{"key": "github_repository", "value": "acmecorp/inventory-service"},
{"key": "environment", "value": "production_us"}
],
expiry=3600 # seconds; default 3600
)
)
client_id = response.client.client_id
plain_secret = response.plain_secret # store this securely; not retrievable again
cURL equivalent (if not using SDK):
curl -X POST "$SCALEKIT_ENVIRONMENT_URL/api/v1/organizations/<ORG_ID>/clients" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <SCALEKIT_ACCESS_TOKEN>" \
-d '{
"name": "GitHub Actions Deployment Service",
"scopes": ["deploy:applications", "read:deployments"],
"audience": ["deployment-api.acmecorp.com"],
"expiry": 3600
}'
Scope naming convention: use
resource:action(e.g.deployments:read,applications:create).
3. API client fetches a bearer token
This step runs inside the API client's code, not your server. Shown here for reference.
curl -X POST "$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>"
Response:
{
"access_token": "<JWT>",
"token_type": "Bearer",
"expires_in": 86399,
"scope": "deploy:applications read:deployments"
}
The client sends this JWT in Authorization: Bearer <JWT> on every API request.
4. Validate the JWT on your API server
Do this on EVERY request. Never trust unverified tokens.
Python (SDK handles JWKS automatically)
token = request.headers.get("Authorization", "").removeprefix("Bearer ")
try:
claims = scalekit_client.validate_access_token_and_get_claims(token=token)
# claims["scopes"] → list of granted scopes
except Exception:
return 401 # invalid or expired
Node.js (manual JWKS + JWT verify)
import jwksClient from 'jwks-rsa';
import jwt from 'jsonwebtoken';
const jwks = jwksClient({
jwksUri: `${process.env.SCALEKIT_ENVIRONMENT_URL}/.well-known/jwks.json`,
cache: true
});
async function verifyToken(token) {
const decoded = jwt.decode(token, { complete: true });
const key = await jwks.getSigningKey(decoded.header.kid);
return jwt.verify(token, key.getPublicKey(), {
algorithms: ['RS256'],
complete: true
}).payload; // contains scopes, sub, iss, exp, oid, etc.
}
Decoded JWT payload structure:
{
"client_id": "m2morg_69038819013296423",
"oid": "org_59615193906282635",
"scopes": ["deploy:applications", "read:deployments"],
"iss": "<SCALEKIT_ENVIRONMENT_URL>",
"exp": 1745305340
}
5. Enforce scopes in middleware
Flask (Python)
import functools
from flask import request, jsonify
def require_scope(scope):
def decorator(f):
@functools.wraps(f)
def wrapper(*args, **kwargs):
token = request.headers.get("Authorization", "").removeprefix("Bearer ")
if not token:
return jsonify({"error": "Missing token"}), 401
try:
claims = scalekit_client.validate_access_token_and_get_claims(token=token)
except Exception:
return jsonify({"error": "Invalid token"}), 401
if scope not in claims.get("scopes", []):
return jsonify({"error": "Insufficient permissions"}), 403
return f(*args, **kwargs)
return wrapper
return decorator
# Usage:
# @app.route('/deploy', methods=['POST'])
# @require_scope('deploy:applications')
# def deploy(): ...
Express (Node.js)
function requireScope(scope) {
return async (req, res, next) => {
const token = (req.headers.authorization || '').replace('Bearer ', '');
if (!token) return res.status(401).send('Missing token');
try {
const payload = await verifyToken(token); // from step 4
if (!payload.scopes?.includes(scope))
return res.status(403).send('Insufficient permissions');
req.tokenClaims = payload;
next();
} catch {
res.status(401).send('Invalid token');
}
};
}
// Usage:
// app.post('/deploy', requireScope('deploy:applications'), handler);
Key rules
plain_secretis returned once only — instruct customers to store it immediately.- Always validate tokens server-side before trusting claims.
- Cache JWKS keys (avoid fetching on every request); rotate on
kidmismatch. - Use
resource:actionscope naming for clarity. - An
organization_idmaps to one customer; multiple API clients per org are supported.
More from scalekit-inc/skills
setup-scalekit
Use when a developer is new to Scalekit and needs guidance on where to start, doesn't know which auth plugin or skill to choose, wants to connect an AI agent or agentic workflow to third-party services (Gmail, Slack, Notion, Google Calendar), needs OAuth or tool-calling auth for agents, wants to add authentication to a project but hasn't chosen an approach yet, or needs to install the Scalekit plugin for their AI coding tool (Claude Code, Codex, Copilot CLI, Cursor, or other agents).
11implementing-scalekit-fsa
Implements Scalekit full-stack authentication (FSA) including sign-up, login, logout, and secure session management using JWT tokens. Use when building or integrating user authentication with the Scalekit SDK across Node.js, Python, Go, or Java — or when the user asks about auth flows, OAuth callbacks, token refresh, or session handling with Scalekit.
4production-readiness-scim
Walks through a structured production readiness checklist for Scalekit SCIM provisioning implementations. Use when the user says they are going live, launching to production, doing a pre-launch review, or wants to verify their SCIM directory sync implementation is production-ready.
2implementing-admin-portal
Implements Scalekit's admin portal for customer self-serve SSO and SCIM configuration. Generates portal links server-side and embeds the portal as an iframe in the app's settings UI. Use when the user asks to add an admin portal, customer self-serve SSO setup, iframe embed for SSO config, shareable setup link, or let customers configure their own SSO or SCIM connection.
2production-readiness-sso
Walks through a structured production readiness checklist for Scalekit SSO implementations. Use when the user says they are going live, launching to production, doing a pre-launch review, hardening their SSO setup, or wants to verify their Scalekit implementation is production-ready.
2production-readiness-agent-auth
Walks through a structured production readiness checklist for Scalekit agent authentication implementations. Use when the user says they are going live, launching to production, doing a pre-launch review, or wants to verify their agent OAuth implementation is production-ready.
2