[TERMINAL · SKILLS]
> mounting /skills...
> indexing 295 manifests...
> linking agents: claude · codex · gemini · cursor
> ready.
[░░░░░░░░░░░░░░░░░░░░░░░░░░░░] 0%
Terminal.skills
Use Cases/Build an OAuth Provider

Build an OAuth Provider

Build an OAuth 2.0 authorization server with authorization code flow, PKCE, client management, scope-based access, token rotation, and developer portal for third-party integrations.

#redis#caching#database#pub-sub#queues
Works with:claude-codeopenai-codexgemini-clicursor

Skills stack · 5 skills

Avg quality 93/100·All SAFE
>

typescript

v

Not yet scored
View skill
>

redis

v1.0.0

Build applications with Redis — caching, session storage, pub/sub, streams, rate limiting, leaderboards, and queues. Use when tasks involve in-memory data storage, real-time messaging, distributed locking, or performance optimization with caching layers.

93/100 quality
1.81× impact
SAFE
View skill
>

postgresql

v1.0.0

Assists with designing schemas, writing performant queries, managing indexes, and operating PostgreSQL databases. Use when working with JSONB, full-text search, window functions, CTEs, row-level security, replication, or performance tuning. Trigger words: postgresql, postgres, sql, database, jsonb, rls, window functions, cte.

87/100 quality
1.53× impact
SAFE
View skill
>

hono

v1.0.0

You are an expert in Hono, the ultrafast web framework for the edge. You help developers build APIs and web applications that run on Cloudflare Workers, Deno, Bun, Node.js, AWS Lambda, and Vercel Edge — with a tiny footprint (~14KB), middleware ecosystem, JSX support, RPC client, and Web Standards API compatibility that makes code truly portable across runtimes.

93/100 quality
3.00× impact
SAFE
View skill
>

zod

v1.0.0

You are an expert in Zod, the TypeScript-first schema declaration and validation library. You help developers define schemas that validate data at runtime AND infer TypeScript types at compile time — eliminating the need to write types and validators separately. Used for API input validation, form validation, environment variables, config files, and any data boundary.

100/100 quality
1.21× impact
SAFE
View skill
$

The Problem

Yuki leads platform at a 35-person SaaS. Third-party developers want to build integrations but there's no secure way to access user data. They're sharing API keys via email — one leaked key gave a contractor access to all customers. Some integrations use a single admin API key for all users (no per-user authorization). They need an OAuth 2.0 provider: developers register apps, users authorize access with specific scopes, tokens auto-rotate, and compromised tokens can be revoked per-user without affecting everyone.

Step 1: Build the OAuth Server

typescript
// src/oauth/server.ts — OAuth 2.0 provider with PKCE, scopes, and token management
import { pool } from "../db";
import { Redis } from "ioredis";
import { randomBytes, createHash } from "node:crypto";

const redis = new Redis(process.env.REDIS_URL!);

interface OAuthClient {
  id: string;
  name: string;
  description: string;
  clientId: string;
  clientSecret: string;
  redirectUris: string[];
  scopes: string[];            // allowed scopes
  type: "confidential" | "public";  // public = SPA/mobile (no secret)
  status: "active" | "suspended";
  rateLimitPerHour: number;
  createdBy: string;
  createdAt: string;
}

interface AuthorizationCode {
  code: string;
  clientId: string;
  userId: string;
  scopes: string[];
  redirectUri: string;
  codeChallenge: string | null;  // PKCE
  codeChallengeMethod: string | null;
  expiresAt: number;
}

interface AccessToken {
  token: string;
  clientId: string;
  userId: string;
  scopes: string[];
  expiresAt: number;
}

const AVAILABLE_SCOPES = [
  { name: "read:profile", description: "Read user profile" },
  { name: "write:profile", description: "Update user profile" },
  { name: "read:data", description: "Read user data" },
  { name: "write:data", description: "Create and update data" },
  { name: "read:billing", description: "View billing information" },
  { name: "webhooks", description: "Manage webhooks" },
];

// Register a new OAuth client (developer portal)
export async function registerClient(params: {
  name: string;
  description: string;
  redirectUris: string[];
  scopes: string[];
  type: OAuthClient["type"];
  createdBy: string;
}): Promise<OAuthClient> {
  const clientId = `cli_${randomBytes(16).toString("hex")}`;
  const clientSecret = params.type === "confidential" ? `sec_${randomBytes(32).toString("hex")}` : "";

  // Validate scopes
  const validScopes = params.scopes.filter((s) => AVAILABLE_SCOPES.some((as) => as.name === s));

  // Validate redirect URIs
  for (const uri of params.redirectUris) {
    const parsed = new URL(uri);
    if (parsed.protocol !== "https:" && parsed.hostname !== "localhost") {
      throw new Error(`Redirect URI must use HTTPS: ${uri}`);
    }
  }

  const client: OAuthClient = {
    id: `oac-${randomBytes(8).toString("hex")}`,
    name: params.name,
    description: params.description,
    clientId,
    clientSecret,
    redirectUris: params.redirectUris,
    scopes: validScopes,
    type: params.type,
    status: "active",
    rateLimitPerHour: 1000,
    createdBy: params.createdBy,
    createdAt: new Date().toISOString(),
  };

  await pool.query(
    `INSERT INTO oauth_clients (id, name, description, client_id, client_secret, redirect_uris, scopes, type, status, rate_limit, created_by, created_at)
     VALUES ($1, $2, $3, $4, $5, $6, $7, $8, 'active', $9, $10, NOW())`,
    [client.id, client.name, client.description, clientId, clientSecret,
     JSON.stringify(client.redirectUris), JSON.stringify(client.scopes),
     client.type, client.rateLimitPerHour, params.createdBy]
  );

  return client;
}

// Authorization endpoint: user consents to share data
export async function authorize(params: {
  clientId: string;
  redirectUri: string;
  responseType: string;
  scopes: string[];
  state: string;
  userId: string;
  codeChallenge?: string;
  codeChallengeMethod?: string;
}): Promise<{ redirectUrl: string }> {
  // Validate client
  const { rows: [client] } = await pool.query(
    "SELECT * FROM oauth_clients WHERE client_id = $1 AND status = 'active'",
    [params.clientId]
  );
  if (!client) throw new Error("Invalid client");

  const redirectUris: string[] = JSON.parse(client.redirect_uris);
  if (!redirectUris.includes(params.redirectUri)) {
    throw new Error("Invalid redirect URI");
  }

  // Validate scopes
  const clientScopes: string[] = JSON.parse(client.scopes);
  const requestedScopes = params.scopes.filter((s) => clientScopes.includes(s));
  if (requestedScopes.length === 0) throw new Error("No valid scopes requested");

  // PKCE required for public clients
  if (client.type === "public" && !params.codeChallenge) {
    throw new Error("PKCE required for public clients");
  }

  // Generate authorization code
  const code = randomBytes(32).toString("hex");
  const authCode: AuthorizationCode = {
    code,
    clientId: params.clientId,
    userId: params.userId,
    scopes: requestedScopes,
    redirectUri: params.redirectUri,
    codeChallenge: params.codeChallenge || null,
    codeChallengeMethod: params.codeChallengeMethod || null,
    expiresAt: Date.now() + 600000, // 10 minutes
  };

  await redis.setex(`oauth:code:${code}`, 600, JSON.stringify(authCode));

  const url = new URL(params.redirectUri);
  url.searchParams.set("code", code);
  url.searchParams.set("state", params.state);

  return { redirectUrl: url.toString() };
}

// Token endpoint: exchange code for tokens
export async function exchangeToken(params: {
  grantType: string;
  code?: string;
  clientId: string;
  clientSecret?: string;
  redirectUri?: string;
  codeVerifier?: string;
  refreshToken?: string;
}): Promise<{
  accessToken: string;
  refreshToken: string;
  tokenType: string;
  expiresIn: number;
  scope: string;
}> {
  // Authenticate client
  const { rows: [client] } = await pool.query(
    "SELECT * FROM oauth_clients WHERE client_id = $1 AND status = 'active'",
    [params.clientId]
  );
  if (!client) throw new Error("Invalid client");

  if (client.type === "confidential") {
    if (client.client_secret !== params.clientSecret) throw new Error("Invalid client credentials");
  }

  if (params.grantType === "authorization_code") {
    if (!params.code) throw new Error("Code required");

    const stored = await redis.get(`oauth:code:${params.code}`);
    if (!stored) throw new Error("Invalid or expired code");
    await redis.del(`oauth:code:${params.code}`); // one-time use

    const authCode: AuthorizationCode = JSON.parse(stored);

    if (authCode.clientId !== params.clientId) throw new Error("Client mismatch");
    if (authCode.redirectUri !== params.redirectUri) throw new Error("Redirect URI mismatch");

    // Verify PKCE
    if (authCode.codeChallenge) {
      if (!params.codeVerifier) throw new Error("Code verifier required");
      const computed = createHash("sha256").update(params.codeVerifier).digest("base64url");
      if (computed !== authCode.codeChallenge) throw new Error("Invalid code verifier");
    }

    return generateTokens(authCode.clientId, authCode.userId, authCode.scopes);
  }

  if (params.grantType === "refresh_token") {
    if (!params.refreshToken) throw new Error("Refresh token required");

    const stored = await redis.get(`oauth:refresh:${params.refreshToken}`);
    if (!stored) throw new Error("Invalid refresh token");

    const refreshData = JSON.parse(stored);

    // Rotate refresh token
    await redis.del(`oauth:refresh:${params.refreshToken}`);

    return generateTokens(refreshData.clientId, refreshData.userId, refreshData.scopes);
  }

  throw new Error("Unsupported grant type");
}

async function generateTokens(clientId: string, userId: string, scopes: string[]) {
  const accessToken = randomBytes(32).toString("hex");
  const refreshToken = randomBytes(32).toString("hex");
  const expiresIn = 3600; // 1 hour

  // Store access token
  await redis.setex(`oauth:access:${accessToken}`, expiresIn, JSON.stringify({
    clientId, userId, scopes, createdAt: Date.now(),
  }));

  // Store refresh token (30 days)
  await redis.setex(`oauth:refresh:${refreshToken}`, 86400 * 30, JSON.stringify({
    clientId, userId, scopes,
  }));

  // Track active grants
  await redis.sadd(`oauth:grants:${userId}`, `${clientId}:${accessToken}`);

  return {
    accessToken, refreshToken,
    tokenType: "Bearer",
    expiresIn,
    scope: scopes.join(" "),
  };
}

// Validate access token (middleware)
export async function validateToken(token: string): Promise<{
  valid: boolean;
  userId?: string;
  clientId?: string;
  scopes?: string[];
}> {
  const stored = await redis.get(`oauth:access:${token}`);
  if (!stored) return { valid: false };

  const { clientId, userId, scopes } = JSON.parse(stored);

  // Rate limit
  const rateKey = `oauth:rate:${clientId}`;
  const count = await redis.incr(rateKey);
  await redis.expire(rateKey, 3600);
  if (count > 1000) return { valid: false };

  return { valid: true, userId, clientId, scopes };
}

// Revoke all tokens for a user-client pair
export async function revokeAccess(userId: string, clientId: string): Promise<void> {
  const grants = await redis.smembers(`oauth:grants:${userId}`);
  for (const grant of grants) {
    if (grant.startsWith(`${clientId}:`)) {
      const token = grant.split(":")[1];
      await redis.del(`oauth:access:${token}`);
      await redis.srem(`oauth:grants:${userId}`, grant);
    }
  }
}

Results

  • Leaked key risk eliminated — each integration uses user-specific tokens with limited scopes; compromising one token doesn't expose other users
  • Third-party ecosystem — 12 integrations built in first quarter; Zapier, Slack, and custom apps connect securely; platform stickiness increased
  • Scope-based access — read-only integrations can't modify data; billing scopes separated from profile scopes; principle of least privilege enforced
  • Token rotation — access tokens expire in 1 hour; refresh tokens rotate on use; leaked tokens have limited lifetime
  • User control — users see which apps have access, what scopes, and when last used; revoke any app in one click without affecting others