agentsclimarketplace

Oauth auth

Skill medy-gribkov/arcana/skills/oauth-auth

Universal AI development toolkit. 74 production-ready skills for every coding agent. Works with Claude Code, Cursor, Codex.

Install
npx -y skills add medy-gribkov/arcana --skill oauth-auth

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

One thing to look at

  • 1 stars1 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

OAuth2, OIDC, JWT validation, session management, PKCE flows, refresh token rotation, and auth middleware

SKILL.md

13.8 KB, ~3.3k tokens by cl100k_base, as published. Nobody here has run it

OAuth and Authentication

Implement secure OAuth2 flows, JWT validation, session management, and authentication patterns.

OAuth2 Authorization Code Flow with PKCE

BAD: No PKCE, state stored in localStorage

// Client initiates OAuth without PKCE
const authUrl = `https://provider.com/oauth/authorize?client_id=${clientId}&redirect_uri=${redirectUri}&response_type=code`;
localStorage.setItem('oauth_state', Math.random().toString());
window.location.href = authUrl;

GOOD: PKCE with httpOnly cookie state

import crypto from 'crypto';

// Generate PKCE challenge
function generatePKCE() {
  const verifier = crypto.randomBytes(32).toString('base64url');
  const challenge = crypto
    .createHash('sha256')
    .update(verifier)
    .digest('base64url');
  return { verifier, challenge };
}

// Initiate OAuth with PKCE
export async function initiateOAuth(res: Response) {
  const { verifier, challenge } = generatePKCE();
  const state = crypto.randomBytes(32).toString('base64url');

  // Store verifier and state in httpOnly cookie
  res.setHeader('Set-Cookie', [
    `pkce_verifier=${verifier}; HttpOnly; Secure; SameSite=Lax; Max-Age=600`,
    `oauth_state=${state}; HttpOnly; Secure; SameSite=Lax; Max-Age=600`
  ]);

  const params = new URLSearchParams({
    client_id: process.env.OAUTH_CLIENT_ID!,
    redirect_uri: process.env.OAUTH_REDIRECT_URI!,
    response_type: 'code',
    scope: 'openid profile email',
    state,
    code_challenge: challenge,
    code_challenge_method: 'S256'
  });

  return `https://provider.com/oauth/authorize?${params}`;
}

Token Exchange and Validation

BAD: No validation, symmetric JWT with weak secret

// Exchange code without validating state
const response = await fetch('https://provider.com/oauth/token', {
  method: 'POST',
  body: JSON.stringify({ code, client_id: clientId, client_secret: secret })
});
const { access_token } = await response.json();

// Weak symmetric JWT
import jwt from 'jsonwebtoken';
const token = jwt.sign({ userId }, 'weak-secret', { expiresIn: '1h' });

GOOD: State validation, PKCE verification, asymmetric JWT

import { SignJWT, jwtVerify, importSPKI, importPKCS8 } from 'jose';

export async function handleOAuthCallback(
  code: string,
  state: string,
  cookies: Record<string, string>
) {
  // Validate state to prevent CSRF
  if (state !== cookies.oauth_state) {
    throw new Error('Invalid state parameter');
  }

  // Exchange code with PKCE verifier
  const response = await fetch('https://provider.com/oauth/token', {
    method: 'POST',
    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
    body: new URLSearchParams({
      grant_type: 'authorization_code',
      code,
      redirect_uri: process.env.OAUTH_REDIRECT_URI!,
      client_id: process.env.OAUTH_CLIENT_ID!,
      code_verifier: cookies.pkce_verifier
    })
  });

  if (!response.ok) {
    throw new Error('Token exchange failed');
  }

  const tokens = await response.json();
  return tokens;
}

// Create asymmetric JWT (RS256)
export async function createAccessToken(payload: Record<string, any>) {
  const privateKey = await importPKCS8(
    process.env.JWT_PRIVATE_KEY!,
    'RS256'
  );

  return await new SignJWT(payload)
    .setProtectedHeader({ alg: 'RS256', typ: 'JWT' })
    .setIssuedAt()
    .setIssuer(process.env.JWT_ISSUER!)
    .setAudience(process.env.JWT_AUDIENCE!)
    .setExpirationTime('15m')
    .sign(privateKey);
}

// Verify JWT with public key
export async function verifyAccessToken(token: string) {
  const publicKey = await importSPKI(
    process.env.JWT_PUBLIC_KEY!,
    'RS256'
  );

  const { payload } = await jwtVerify(token, publicKey, {
    issuer: process.env.JWT_ISSUER!,
    audience: process.env.JWT_AUDIENCE!
  });

  return payload;
}

Refresh Token Rotation with Reuse Detection

BAD: No rotation, long-lived refresh tokens

// Single refresh token, no rotation
const refreshToken = crypto.randomBytes(32).toString('hex');
await db.saveRefreshToken(userId, refreshToken, { expiresIn: '30d' });

GOOD: Rotation with family tracking and reuse detection

interface RefreshToken {
  tokenHash: string;
  userId: string;
  family: string;
  expiresAt: Date;
  revokedAt?: Date;
  replacedBy?: string;
}

export async function createRefreshToken(userId: string, family?: string) {
  const token = crypto.randomBytes(32).toString('base64url');
  const tokenHash = crypto.createHash('sha256').update(token).digest('hex');
  const tokenFamily = family || crypto.randomBytes(16).toString('hex');

  await db.refreshTokens.create({
    tokenHash,
    userId,
    family: tokenFamily,
    expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000) // 7 days
  });

  return { token, family: tokenFamily };
}

export async function rotateRefreshToken(token: string) {
  const tokenHash = crypto.createHash('sha256').update(token).digest('hex');
  const existing = await db.refreshTokens.findOne({ tokenHash });

  if (!existing || existing.expiresAt < new Date()) {
    throw new Error('Invalid or expired refresh token');
  }

  // Reuse detection: if token already replaced, revoke entire family
  if (existing.revokedAt || existing.replacedBy) {
    await db.refreshTokens.updateMany(
      { family: existing.family },
      { revokedAt: new Date() }
    );
    throw new Error('Token reuse detected, family revoked');
  }

  // Create new token in same family
  const { token: newToken, family } = await createRefreshToken(
    existing.userId,
    existing.family
  );

  // Mark old token as replaced
  await db.refreshTokens.updateOne(
    { tokenHash },
    {
      revokedAt: new Date(),
      replacedBy: crypto.createHash('sha256').update(newToken).digest('hex')
    }
  );

  return { accessToken: await createAccessToken({ userId: existing.userId }), refreshToken: newToken };
}

Session Management

BAD: Tokens in localStorage, no CSRF protection

// Client stores tokens in localStorage
localStorage.setItem('access_token', accessToken);
localStorage.setItem('refresh_token', refreshToken);

// No CSRF protection
fetch('/api/sensitive', {
  headers: { Authorization: `Bearer ${localStorage.getItem('access_token')}` }
});

GOOD: httpOnly cookies with CSRF tokens

import { serialize } from 'cookie';

export function setAuthCookies(
  res: Response,
  accessToken: string,
  refreshToken: string
) {
  const csrfToken = crypto.randomBytes(32).toString('base64url');

  res.setHeader('Set-Cookie', [
    serialize('access_token', accessToken, {
      httpOnly: true,
      secure: process.env.NODE_ENV === 'production',
      sameSite: 'lax',
      maxAge: 15 * 60, // 15 minutes
      path: '/'
    }),
    serialize('refresh_token', refreshToken, {
      httpOnly: true,
      secure: process.env.NODE_ENV === 'production',
      sameSite: 'lax',
      maxAge: 7 * 24 * 60 * 60, // 7 days
      path: '/api/auth/refresh'
    }),
    serialize('csrf_token', csrfToken, {
      httpOnly: false, // Accessible to JS for request headers
      secure: process.env.NODE_ENV === 'production',
      sameSite: 'lax',
      maxAge: 15 * 60,
      path: '/'
    })
  ]);

  return csrfToken;
}

// Middleware validates CSRF token
export function validateCSRF(req: Request) {
  const cookieCSRF = req.cookies.csrf_token;
  const headerCSRF = req.headers.get('X-CSRF-Token');

  if (!cookieCSRF || cookieCSRF !== headerCSRF) {
    throw new Error('CSRF validation failed');
  }
}

NextAuth.js/Auth.js Pattern

import NextAuth from 'next-auth';
import GoogleProvider from 'next-auth/providers/google';
import { JWT } from 'next-auth/jwt';

export const { handlers, auth, signIn, signOut } = NextAuth({
  providers: [
    GoogleProvider({
      clientId: process.env.GOOGLE_CLIENT_ID!,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
      authorization: {
        params: {
          prompt: 'consent',
          access_type: 'offline',
          response_type: 'code'
        }
      }
    })
  ],
  session: {
    strategy: 'jwt',
    maxAge: 7 * 24 * 60 * 60 // 7 days
  },
  callbacks: {
    async jwt({ token, account, user }) {
      if (account && user) {
        return {
          ...token,
          accessToken: account.access_token,
          refreshToken: account.refresh_token,
          accessTokenExpires: account.expires_at! * 1000,
          userId: user.id
        };
      }

      // Return previous token if access token not expired
      if (Date.now() < (token.accessTokenExpires as number)) {
        return token;
      }

      // Refresh access token
      return await refreshAccessToken(token);
    },
    async session({ session, token }) {
      session.user.id = token.userId as string;
      session.accessToken = token.accessToken as string;
      return session;
    }
  }
});

async function refreshAccessToken(token: JWT) {
  try {
    const response = await fetch('https://oauth2.googleapis.com/token', {
      method: 'POST',
      headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
      body: new URLSearchParams({
        client_id: process.env.GOOGLE_CLIENT_ID!,
        client_secret: process.env.GOOGLE_CLIENT_SECRET!,
        grant_type: 'refresh_token',
        refresh_token: token.refreshToken as string
      })
    });

    const refreshed = await response.json();

    if (!response.ok) {
      throw refreshed;
    }

    return {
      ...token,
      accessToken: refreshed.access_token,
      accessTokenExpires: Date.now() + refreshed.expires_in * 1000,
      refreshToken: refreshed.refresh_token ?? token.refreshToken
    };
  } catch (error) {
    return {
      ...token,
      error: 'RefreshAccessTokenError'
    };
  }
}

Password Hashing with bcrypt

BAD: Plain text or weak hashing

// Plain text password
await db.users.create({ email, password });

// Weak hash
import crypto from 'crypto';
const hash = crypto.createHash('md5').update(password).digest('hex');

GOOD: bcrypt with salt rounds

import bcrypt from 'bcrypt';

const SALT_ROUNDS = 12;

export async function hashPassword(password: string): Promise<string> {
  return await bcrypt.hash(password, SALT_ROUNDS);
}

export async function verifyPassword(
  password: string,
  hash: string
): Promise<boolean> {
  return await bcrypt.compare(password, hash);
}

// Usage in registration
export async function registerUser(email: string, password: string) {
  const existingUser = await db.users.findOne({ email });
  if (existingUser) {
    throw new Error('User already exists');
  }

  const passwordHash = await hashPassword(password);
  return await db.users.create({ email, passwordHash });
}

Authentication Middleware

import { NextRequest, NextResponse } from 'next/server';

export async function authMiddleware(req: NextRequest) {
  const token = req.cookies.get('access_token')?.value;

  if (!token) {
    return NextResponse.redirect(new URL('/login', req.url));
  }

  try {
    const payload = await verifyAccessToken(token);

    // Attach user to request headers for route handlers
    const requestHeaders = new Headers(req.headers);
    requestHeaders.set('X-User-Id', payload.userId as string);

    return NextResponse.next({
      request: { headers: requestHeaders }
    });
  } catch (error) {
    // Token invalid or expired, try refresh
    const refreshToken = req.cookies.get('refresh_token')?.value;

    if (!refreshToken) {
      return NextResponse.redirect(new URL('/login', req.url));
    }

    try {
      const { accessToken, refreshToken: newRefreshToken } =
        await rotateRefreshToken(refreshToken);

      const response = NextResponse.next();
      setAuthCookies(response, accessToken, newRefreshToken);

      return response;
    } catch (refreshError) {
      return NextResponse.redirect(new URL('/login', req.url));
    }
  }
}

// middleware.ts
export { authMiddleware as middleware };

export const config = {
  matcher: ['/dashboard/:path*', '/api/protected/:path*']
};

Token Revocation

// Revoke all tokens for a user (on password change, logout all devices)
export async function revokeAllUserTokens(userId: string) {
  await db.refreshTokens.updateMany(
    { userId, revokedAt: null },
    { revokedAt: new Date() }
  );
}

// Revoke specific token
export async function revokeRefreshToken(token: string) {
  const tokenHash = crypto.createHash('sha256').update(token).digest('hex');
  await db.refreshTokens.updateOne(
    { tokenHash },
    { revokedAt: new Date() }
  );
}

// Cleanup expired tokens (run daily)
export async function cleanupExpiredTokens() {
  const cutoff = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000); // 30 days
  await db.refreshTokens.deleteMany({
    expiresAt: { $lt: cutoff }
  });
}

OIDC ID Token Validation

import { jwtVerify, createRemoteJWKSet } from 'jose';

export async function validateIDToken(
  idToken: string,
  clientId: string,
  issuer: string
) {
  const JWKS = createRemoteJWKSet(
    new URL(`${issuer}/.well-known/jwks.json`)
  );

  const { payload } = await jwtVerify(idToken, JWKS, {
    issuer,
    audience: clientId,
    maxTokenAge: '10m'
  });

  // Validate nonce if using implicit flow
  if (payload.nonce) {
    // Compare with stored nonce from initial request
  }

  return {
    sub: payload.sub,
    email: payload.email as string,
    email_verified: payload.email_verified as boolean,
    name: payload.name as string
  };
}

Run workflow: detect auth pattern needed, choose flow (OAuth2 code + PKCE for third-party, session cookies for first-party), implement token storage with httpOnly cookies, add CSRF protection, implement refresh rotation with reuse detection, add middleware guards, validate all tokens with asymmetric keys.

Gives 1 of the 12 instructions most auth identity skills give in ~3.3k tokens

Counted across 409 of the 410 authors here whose files we hold, read 2026-08-06

  • hash passwords with bcrypt or argon2in 53 of 409, across 43 files
  • use parameterized queriesin 47 of 409, across 39 files
  • load SECRET_KEY from environment variablesin 23 of 409, across 14 files
  • validate all input server-sidein 19 of 409, across 11 files
  • refresh access tokens before expiryin 17 of 409, across 9 files
  • store tokens in httponly cookieshere, and in 17 of 409, across 16 files
  • store refresh tokens securelyin 16 of 409, across 6 files
  • validate webhook signatures before processingin 15 of 409, across 5 files
  • sanitize user inputsin 15 of 409, across 9 files
  • implement rate limiting on auth endpointsin 14 of 409, across 9 files
  • encrypt sensitive data at restin 13 of 409, across 10 files
  • validate uploaded file extensions and sizesin 12 of 409, across 5 files

Said here and by no other author read

  • use asymmetric algorithms for JWTs

Grouped from the skills themselves: near-identical wordings counted once, and counted by distinct author, so one author publishing three of these counts once. Length counted with cl100k_base; the agent that loads this file may tokenize it differently.

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.