skills/adding-oauth2-to-apis/SKILL.md
Implements OAuth 2.0 client-credentials authentication on API endpoints using Scalekit as the authorization server. Use when protecting APIs with machine-to-machine auth, registering API clients for organizations, issuing bearer tokens, validating JWTs via JWKS, or enforcing scopes in middleware.
npx skillsauth add scalekit-inc/skills adding-oauth2-to-apisInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
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.
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.
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).
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.
Do this on EVERY request. Never trust unverified tokens.
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
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
}
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(): ...
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);
plain_secret is returned once only — instruct customers to store it immediately.kid mismatch.resource:action scope naming for clarity.organization_id maps to one customer; multiple API clients per org are supported.tools
Create or review Scalekit custom providers/connectors for proxy-only usage, including MCP providers. Use this skill when the task is to gather API docs, infer whether a connector is OAuth, Basic, Bearer, or API Key, determine if it is an MCP provider, determine required tracked fields like domain or version, generate provider JSON, check for existing custom providers, show update diffs, run approved create or update curls, and print resolved delete curls.
tools
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).
tools
Use when a user asks to generate, review, validate, or fix any code snippet that uses Scalekit APIs or SDKs. This skill is the single source of truth for Scalekit code correctness — it can generate illustration-quality snippets from scratch (for docs, websites, or integration guides) and review existing code to catch wrong method names, missing parameters, security anti-patterns, and broken auth flows. Covers all four SDKs (Node, Python, Go, Java), raw REST API calls, and both Scalekit product suites — SaaSKit (SSO, login, sessions, RBAC, SCIM) and AgentKit (connections, tool calling, MCP auth). Use when the user says review my Scalekit code, generate a Scalekit example, validate this auth flow, check my SDK usage, fix my Scalekit integration, write a code sample for docs, or anything involving Scalekit code quality.
development
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.