engineering/auth-security/skills/authentication-patterns/SKILL.md
This skill should be used when the user asks to "implement authentication", "add JWT", "set up OAuth", "handle sessions", or "design login flow". Also trigger for "how do I authenticate users", "implement SSO", "add MFA", "configure API keys", or when the user needs to choose between authentication strategies.
npx skillsauth add harsh040506/claude-code-unified-skill-plugin-library engineering/auth-security/skills/authentication-patternsInstall 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.
| Situation | Recommended Strategy | |-----------|---------------------| | User sessions in a web app | JWT (short-lived) + refresh token rotation | | Machine-to-machine API calls | API keys with HMAC signing or OAuth 2.0 client credentials | | Third-party login ("Login with Google") | OAuth 2.0 Authorization Code + PKCE | | Internal microservice-to-service | mTLS or short-lived service tokens issued by a vault | | High-security admin actions | MFA + session re-authentication | | Non-interactive CI/CD pipelines | Short-lived OIDC tokens from the CI provider |
Never use long-lived shared secrets for anything that touches production data.
Issue tokens with the smallest possible lifetime for the threat model:
Access token: 5–15 minutes
Refresh token: 7–30 days (rotating on every use)
Service token: 1 hour max, no refresh
Validate on every request — do not trust cached validation results for longer than the token's TTL:
import jwt
from datetime import datetime, timezone
def validate_token(token: str, public_key: str, audience: str) -> dict:
try:
payload = jwt.decode(
token,
public_key,
algorithms=["RS256"], # Always specify algorithms explicitly
audience=audience, # Validate aud claim
options={"require": ["exp", "iat", "sub", "aud", "jti"]}
)
except jwt.ExpiredSignatureError:
raise AuthError("Token expired")
except jwt.InvalidTokenError as e:
raise AuthError(f"Invalid token: {e}")
# Check jti against revocation list for high-value operations
if is_revoked(payload["jti"]):
raise AuthError("Token revoked")
return payload
Critical: Use RS256 (asymmetric) for tokens shared across services. Only use HS256 when a single service both signs and verifies.
Never put sensitive data (PII, roles that shouldn't be public) in the payload — JWT claims are base64-encoded, not encrypted.
PKCE (Proof Key for Code Exchange) is mandatory for all public clients (SPAs, mobile apps):
import secrets, hashlib, base64
def generate_pkce_pair() -> tuple[str, str]:
"""Returns (code_verifier, code_challenge)."""
verifier = secrets.token_urlsafe(64) # 43–128 chars
digest = hashlib.sha256(verifier.encode()).digest()
challenge = base64.urlsafe_b64encode(digest).rstrip(b"=").decode()
return verifier, challenge
# Redirect to authorization server
auth_url = (
f"{ISSUER}/authorize"
f"?response_type=code"
f"&client_id={CLIENT_ID}"
f"&redirect_uri={REDIRECT_URI}"
f"&scope=openid+profile+email"
f"&state={secrets.token_urlsafe(32)}" # CSRF protection
f"&code_challenge={challenge}"
f"&code_challenge_method=S256"
)
Validate state on the callback — a missing or mismatched state indicates a CSRF attempt.
Refresh tokens must rotate on every use. Detect reuse (a rotated token being replayed) and immediately invalidate the entire token family:
def refresh_access_token(refresh_token: str) -> TokenPair:
family_id, token_hash = parse_refresh_token(refresh_token)
family = db.get_token_family(family_id)
if family is None or family.is_revoked:
raise AuthError("Token family revoked")
if not secrets.compare_digest(family.current_token_hash, token_hash):
# Reuse detected — revoke entire family immediately
db.revoke_token_family(family_id)
raise AuthError("Refresh token reuse detected — all sessions invalidated")
new_access, new_refresh = issue_token_pair(family.user_id)
db.rotate_token_family(family_id, new_refresh_hash=hash(new_refresh))
return TokenPair(access=new_access, refresh=new_refresh)
Prefer server-side sessions for highly sensitive applications where token revocation must be instant:
# Session ID: cryptographically random, not guessable
session_id = secrets.token_hex(32)
# Store only the session ID in the cookie
response.set_cookie(
"session_id",
session_id,
httponly=True, # No JS access
secure=True, # HTTPS only
samesite="Lax", # CSRF mitigation
max_age=3600, # 1-hour idle timeout
path="/",
)
# Store session data server-side (Redis with TTL)
redis.setex(f"session:{session_id}", 3600, serialize(session_data))
Regenerate the session ID on every privilege escalation (login, permission change) to prevent session fixation.
Structure API keys to be identifiable without storing them in plaintext:
Format: {prefix}_{random_bytes}
Example: sk_live_4f9a2b8e3c1d7f6a...
prefix: identifies the environment (sk_live_, sk_test_, pk_)
random_bytes: 32+ bytes from secrets.token_urlsafe()
Storage pattern — store only the hash, never the raw key:
import hashlib, secrets
def create_api_key(user_id: str) -> tuple[str, str]:
"""Returns (raw_key_to_show_once, key_id_for_db)."""
raw_key = f"sk_live_{secrets.token_urlsafe(32)}"
key_hash = hashlib.sha256(raw_key.encode()).hexdigest()
key_id = db.insert_api_key(user_id=user_id, hash=key_hash)
return raw_key, key_id # raw_key shown once, then discarded
def verify_api_key(raw_key: str) -> dict:
key_hash = hashlib.sha256(raw_key.encode()).hexdigest()
record = db.find_api_key_by_hash(key_hash)
if record is None or record.is_revoked:
raise AuthError("Invalid API key")
return record
Support TOTP (RFC 6238) as the baseline factor. Backup codes are required:
import pyotp, secrets
def setup_totp(user_id: str) -> dict:
secret = pyotp.random_base32() # 160-bit secret
totp = pyotp.TOTP(secret)
provisioning_uri = totp.provisioning_uri(
name=get_user_email(user_id),
issuer_name="MyApp"
)
# Store encrypted secret; return once for QR code
store_encrypted_totp_secret(user_id, secret)
return {"provisioning_uri": provisioning_uri}
def verify_totp(user_id: str, code: str) -> bool:
secret = get_totp_secret(user_id)
totp = pyotp.TOTP(secret)
# valid_window=1 allows ±30 seconds clock drift
return totp.verify(code, valid_window=1)
Generate 8–12 single-use backup codes on enrollment; hash them individually and store only the hashes.
| Mistake | Why It Fails | Correct Approach |
|---------|-------------|-----------------|
| Storing JWTs in localStorage | Accessible to XSS | Use httpOnly cookies |
| Using == for token comparison | Timing oracle | Use secrets.compare_digest() |
| Long-lived refresh tokens without rotation | Stolen token stays valid forever | Rotate on every use |
| Symmetric JWT for cross-service auth | All services share the secret | Use RS256 with asymmetric keys |
| No aud claim validation | Token for service A accepted by service B | Always validate audience |
| Trusting alg: none in JWT header | Algorithm confusion attack | Fix algorithm on decode |
| Session IDs in URL params | Leaked in server logs, referrer headers | Always use cookies |
For complete implementation patterns, token schema examples, and OAuth flow diagrams, see:
references/token-patterns.md — JWT / refresh token schema, signing key rotationreferences/oauth-flows.md — Authorization code, client credentials, device flowtesting
Performs quality control on single-cell RNA-seq data (.h5ad or .h5 files) using scverse best practices with MAD-based filtering and comprehensive visualizations. Use when users request QC analysis, filtering low-quality cells, assessing data quality, or following scverse/scanpy best practices for single-cell analysis.
tools
Deep learning for single-cell analysis using scvi-tools. This skill should be used when users need (1) data integration and batch correction with scVI/scANVI, (2) ATAC-seq analysis with PeakVI, (3) CITE-seq multi-modal analysis with totalVI, (4) multiome RNA+ATAC analysis with MultiVI, (5) spatial transcriptomics deconvolution with DestVI, (6) label transfer and reference mapping with scANVI/scArches, (7) RNA velocity with veloVI, or (8) any deep learning-based single-cell method. Triggers include mentions of scVI, scANVI, totalVI, PeakVI, MultiVI, DestVI, veloVI, sysVI, scArches, variational autoencoder, VAE, batch correction, data integration, multi-modal, CITE-seq, multiome, reference mapping, latent space.
testing
This skill should be used when scientists need help with research problem selection, project ideation, troubleshooting stuck projects, or strategic scientific decisions. Use this skill when users ask to pitch a new research idea, work through a project problem, evaluate project risks, plan research strategy, navigate decision trees, or get help choosing what scientific problem to work on. Typical requests include "I have an idea for a project", "I'm stuck on my research", "help me evaluate this project", "what should I work on", or "I need strategic advice about my research".
development
Run nf-core bioinformatics pipelines (rnaseq, sarek, atacseq) on sequencing data. Use when analyzing RNA-seq, WGS/WES, or ATAC-seq data—either local FASTQs or public datasets from GEO/SRA. Triggers on nf-core, Nextflow, FASTQ analysis, variant calling, gene expression, differential expression, GEO reanalysis, GSE/GSM/SRR accessions, or samplesheet creation.