agentsclimarketplace

Adding oauth2 to apis

Skill scalekit-inc/skills/skills/adding-oauth2-to-apis

35 skills that teach AI coding agents to integrate Scalekit auth — agent auth, full-stack login, MCP OAuth 2.1, enterprise SSO, and SCIM. Works with Claude Code, Cursor, Windsurf, and 35+ other agents.

Install
npx -y skills add scalekit-inc/skills --skill adding-oauth2-to-apis

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

2 things to look at

  • no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
  • 2 stars2 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

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.

SKILL.md

6.4 KB, as published. Nobody here has run it

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_secretplain_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_secret is 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 kid mismatch.
  • Use resource:action scope naming for clarity.
  • An organization_id maps to one customer; multiple API clients per org are supported.

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.