kid-sid/claude-spellbook/auth

Claude Code and agent skill by kid-sid/claude-spellbook

Trust tier 3
  • community
Source check pending

Checking that the SKILL.md file is still in the repository.

Use when implementing login flows, issuing or validating JWTs, setting up OAuth2/OIDC with a provider, designing role-based or attribute-based access control, securing API endpoints, or handling token refresh and revocation.

Install

npx vskill@latest install kid-sid/claude-spellbook/auth

SKILL.md

Authentication & Authorization

Patterns for identity, token management, and access control across web APIs and services.

When to Activate

  • Implementing login, logout, or registration flows
  • Issuing, validating, or refreshing JWTs
  • Integrating an OAuth2/OIDC provider (Google, GitHub, Auth0, Keycloak)
  • Designing role-based (RBAC) or attribute-based (ABAC) access control
  • Securing REST or GraphQL endpoints with middleware/guards
  • Handling token revocation, rotation, or blacklisting
  • Auditing an existing auth implementation for security gaps

Core Concepts

Authentication vs. Authorization
ConceptQuestion answeredExample
AuthenticationWho are you?Login with email + password
AuthorizationWhat can you do?Admin can delete; viewer can only read
IdentityWhat do we know about you?Email, roles, tenant ID in the token
Token Types
TypeStorageLifespanUse for
Access token (JWT)Memory / header5–60 minAPI calls
Refresh token (opaque)HttpOnly cookieDays–weeksObtain new access tokens
Session cookieHttpOnly cookieSession or slidingTraditional web apps
API keyServer-side onlyLong-livedM2M, developer integrations

JWT Patterns

Structure and Signing
python
# Python — PyJWT
import jwt
from datetime import datetime, timedelta, UTC
SECRET = "..." # use RS256 with a key pair in production
def issue_token(user_id: str, roles: list[str]) -> str:
return jwt.encode(
{
"sub": user_id,
"roles": roles,
"iat": datetime.now(UTC),
"exp": datetime.now(UTC) + timedelta(minutes=15),
},
SECRET,
algorithm="HS256",
)
def verify_token(token: str) -> dict:
return jwt.decode(token, SECRET, algorithms=["HS256"])
typescript
// TypeScript — jose
import { SignJWT, jwtVerify } from "jose";
const secret = new TextEncoder().encode(process.env.JWT_SECRET);
async function issueToken(userId: string, roles: string[]): Promise<string> {
return new SignJWT({ sub: userId, roles })
.setProtectedHeader({ alg: "HS256" })
.setIssuedAt()
.setExpirationTime("15m")
.sign(secret);
}
async function verifyToken(token: string) {
const { payload } = await jwtVerify(token, secret);
return payload;
}
go
// Go — golang-jwt/jwt
import (
"github.com/golang-jwt/jwt/v5"
"time"
)
type Claims struct {
Roles []string `json:"roles"`
jwt.RegisteredClaims
}
func IssueToken(userID string, roles []string, secret []byte) (string, error) {
claims := Claims{
Roles: roles,
RegisteredClaims: jwt.RegisteredClaims{
Subject: userID,
IssuedAt: jwt.NewNumericDate(time.Now()),
ExpiresAt: jwt.NewNumericDate(time.Now().Add(15 * time.Minute)),
},
}
return jwt.NewWithClaims(jwt.SigningMethodHS256, claims).SignedString(secret)
}
RS256 vs HS256
HS256RS256
Key typeShared secretPrivate/public key pair
Who can verifyAnyone with the secretAnyone with the public key
Best forSingle serviceMicroservices, public JWKS endpoint
RotationRequires coordinated redeployRotate private key; publish new JWKS

Use RS256 in production when multiple services verify tokens, or when tokens are issued by an identity provider.


OAuth2 / OIDC

Flow Selection
FlowUse whenNotes
Authorization Code + PKCEBrowser SPA, mobile appNo client secret on device
Authorization CodeServer-side web appStore client secret server-side
Client CredentialsM2M / service accountsNo user involved
Device CodeCLI tools, smart TVsUser authenticates on a second device

Never use Implicit flow — it is deprecated (RFC 9700).

Authorization Code + PKCE (SPA)
typescript
// 1. Generate PKCE values
function generateCodeVerifier(): string {
const array = new Uint8Array(32);
crypto.getRandomValues(array);
return btoa(String.fromCharCode(...array)).replace(/[+/=]/g, (c) =>
({ "+": "-", "/": "_", "=": "" })[c]!
);
}
async function generateCodeChallenge(verifier: string): Promise<string> {
const data = new TextEncoder().encode(verifier);
const digest = await crypto.subtle.digest("SHA-256", data);
return btoa(String.fromCharCode(...new Uint8Array(digest)))
.replace(/[+/=]/g, (c) => ({ "+": "-", "/": "_", "=": "" })[c]!);
}
// 2. Redirect to provider
const verifier = generateCodeVerifier();
sessionStorage.setItem("pkce_verifier", verifier);
const challenge = await generateCodeChallenge(verifier);
const params = new URLSearchParams({
response_type: "code",
client_id: CLIENT_ID,
redirect_uri: REDIRECT_URI,
scope: "openid profile email",
code_challenge: challenge,
code_challenge_method: "S256",
state: crypto.randomUUID(), // store and verify on return
});
window.location.href = `${PROVIDER_URL}/authorize?${params}`;
// 3. Exchange code for tokens (on redirect back)
async function handleCallback(code: string): Promise<void> {
const verifier = sessionStorage.getItem("pkce_verifier")!;
const res = await fetch(`${PROVIDER_URL}/token`, {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: new URLSearchParams({
grant_type: "authorization_code",
code,
redirect_uri: REDIRECT_URI,
client_id: CLIENT_ID,
code_verifier: verifier,
}),
});
const { access_token, refresh_token, id_token } = await res.json();
// store access_token in memory, refresh_token in HttpOnly cookie via backend
}
Client Credentials (M2M)
python
# Python — httpx
import httpx
def get_m2m_token(client_id: str, client_secret: str, token_url: str) -> str:
r = httpx.post(
token_url,
data={
"grant_type": "client_credentials",
"client_id": client_id,
"client_secret": client_secret,
"scope": "api:read api:write",
},
)
r.raise_for_status()
return r.json()["access_token"]

Token Storage

LocationXSS safeCSRF safeNotes
Memory (JS variable)YesYesLost on page refresh; best for SPAs
HttpOnly cookieYesNo — add CSRF tokenBest for refresh tokens
localStorageNoYesNever store tokens here
sessionStorageNoYesCleared on tab close; still XSS-vulnerable

Rule: Store access tokens in memory. Store refresh tokens in HttpOnly, SameSite=Strict cookies. Never put tokens in localStorage.


Token Refresh and Revocation

Silent Refresh Pattern
typescript
let accessToken: string | null = null;
async function getValidToken(): Promise<string> {
if (accessToken && !isExpiringSoon(accessToken)) return accessToken;
const res = await fetch("/auth/refresh", {
method: "POST",
credentials: "include", // sends HttpOnly refresh token cookie
});
if (!res.ok) {
// refresh token expired — redirect to login
window.location.href = "/login";
throw new Error("Session expired");
}
accessToken = (await res.json()).access_token;
return accessToken;
}
function isExpiringSoon(token: string): boolean {
const { exp } = JSON.parse(atob(token.split(".")[1]));
return exp * 1000 - Date.now() < 60_000; // refresh if < 60s left
}
Revocation Strategies
StrategyHowTrade-off
Short expiry5–15 min access tokensNo revocation needed; stale window is small
Token blacklistStore revoked JTIs in RedisInstant revocation; requires Redis lookup per request
Refresh token rotationIssue new refresh token on each use; invalidate oldDetects theft; one-time-use tokens
Opaque tokens + introspectionValidate tokens against auth serverInstant revocation; adds latency

Access Control (RBAC / ABAC)

RBAC Middleware
python
# FastAPI
from functools import wraps
from fastapi import Depends, HTTPException, status
from typing import Callable
def require_roles(*roles: str) -> Callable:
def dependency(token_data: dict = Depends(get_current_user)):
user_roles = set(token_data.get("roles", []))
if not user_roles.intersection(roles):
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN)
return token_data
return dependency
@router.delete("/users/{user_id}")
async def delete_user(
user_id: str,
_=Depends(require_roles("admin")),
):
...
typescript
// Express middleware
function requireRoles(...roles: string[]) {
return (req: Request, res: Response, next: NextFunction) => {
const userRoles: string[] = res.locals.user?.roles ?? [];
if (!roles.some((r) => userRoles.includes(r))) {
return res.status(403).json({ error: "Forbidden" });
}
next();
};
}
router.delete("/users/:id", requireRoles("admin"), deleteUserHandler);
ABAC Policy Check
python
# Simple policy engine
def can(user: dict, action: str, resource: dict) -> bool:
if "admin" in user["roles"]:
return True
if action == "read" and resource["public"]:
return True
if action in ("update", "delete") and resource["owner_id"] == user["id"]:
return True
return False
# Usage
if not can(current_user, "delete", post):
raise HTTPException(status_code=403)

Password Hashing

Always use a slow, salted hashing algorithm. Never use MD5, SHA-1, or SHA-256 for passwords.

AlgorithmLibrary (Python)Library (Node)Recommended?
bcryptbcryptbcrypt / argon2-browserYes
Argon2idargon2-cffiargon2-browserYes — preferred
scryptstdlib hashlibstdlib cryptoYes
PBKDF2stdlib hashlibstdlib cryptoAcceptable
MD5 / SHA-*——Never
python
# Python — argon2-cffi
from argon2 import PasswordHasher
ph = PasswordHasher()
def hash_password(password: str) -> str:
return ph.hash(password)
def verify_password(hashed: str, password: str) -> bool:
try:
return ph.verify(hashed, password)
except Exception:
return False

Red Flags

  • Storing tokens in localStorage — XSS can steal every token; use HttpOnly cookies for refresh tokens and in-memory for access tokens.
  • Using HS256 across multiple services — all services share the secret; a compromise of one exposes all; use RS256 with a JWKS endpoint.
  • Long-lived access tokens — a 24-hour JWT cannot be revoked without a blacklist; keep them under 15 minutes.
  • Skipping `state` parameter in OAuth2 — omitting state enables CSRF attacks on the callback endpoint.
  • Rolling your own crypto — never implement JWT signing, hashing, or encryption from scratch; use audited libraries.
  • Trusting the `alg` header from the token — an attacker can set alg: none; always pin the algorithm server-side.
  • Returning 404 instead of 403 — security-by-obscurity doesn't prevent enumeration; return 403 Forbidden for authorization failures.
  • No token rotation on refresh — refresh tokens that never rotate are permanent credentials; rotate on every use and invalidate the old one.
  • Putting secrets in JWTs — JWTs are base64-encoded, not encrypted; any party with the token can read the payload.
  • Broad OAuth scopes — request only the minimum scopes needed; * or admin scopes violate least privilege.

Checklist

This is the start of the file. Read the full SKILL.md on GitHub.

Security

  • Security scanPassed, 95 of 100Pattern checks on the skill's files.
  • Model reviewPassed

Quality evaluation

  • Task evaluationNot runNo evaluation yet. Run one in Skill Studio.

Source

Popularity

Repository stars
186
Repository forks
15
7-day trend score
26

Stars and forks are for the whole GitHub repository, not this skill alone. Not refreshed yet.

Works with

Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.

  • Claude Code
  • Cursor
  • GitHub Copilot
  • Windsurf
  • Codex
  • Gemini CLI
  • Cline
  • Amp
  • Roo Code
  • Goose
  • Continue
  • Kilo Code
  • Junie
  • Trae
  • OpenHands
  • Qwen Code
  • AI Agent
  • Tabnine Chat
  • Sourcegraph Cody
  • Amazon Q
  • Firebase Genkit
  • Sweep
  • Devin
  • Phind
  • Mentat
  • Claude Desktop
  • Aider
  • GPT Pilot
  • Plandex
  • bolt.new
  • v0
  • Replit Agent
  • Anse
  • TypingMind
  • BetterChatGPT
  • LibreChat
  • Warp AI
  • Pieces
  • Fig AI
Embed badge
vskill VERIFIED badge
[![vskill VERIFIED](https://verified-skill.com/api/v1/skills/kid-sid/claude-spellbook/auth/badge)](https://verified-skill.com/skills/kid-sid/claude-spellbook/auth)

Something wrong with this skill or its page? Tell us.