Agent Skills Framework Extension
Authentication & Authorization Skill
When to Use This Skill
Use this skill when implementing authentication authorization patterns in your codebase.
How to Use This Skill
- Review the patterns and examples below
- Apply the relevant patterns to your implementation
- Follow the best practices outlined in this skill
JWT, OAuth2, RBAC, session management, and secure authentication patterns for production applications.
Core Capabilities
- JWT Authentication - Stateless token-based auth with refresh tokens
- OAuth2 Integration - Social login, authorization code flow
- RBAC - Role-based access control with permissions
- Session Management - Secure cookie-based sessions
- MFA - TOTP, WebAuthn, backup codes
JWT Implementation
Token Service (TypeScript)
// src/services/jwt.service.ts
import jwt, { JwtPayload, SignOptions } from 'jsonwebtoken';
import { createHash, randomBytes } from 'crypto';
interface TokenPayload {
userId: string;
email: string;
roles: string[];
tenantId?: string;
}
interface TokenPair {
accessToken: string;
refreshToken: string;
expiresIn: number;
}
export class JwtService {
private readonly accessSecret: string;
private readonly refreshSecret: string;
private readonly accessExpiresIn: number;
private readonly refreshExpiresIn: number;
constructor() {
this.accessSecret = process.env.JWT_ACCESS_SECRET!;
this.refreshSecret = process.env.JWT_REFRESH_SECRET!;
this.accessExpiresIn = 15 * 60; // 15 minutes
this.refreshExpiresIn = 7 * 24 * 60 * 60; // 7 days
}
generateTokenPair(payload: TokenPayload): TokenPair {
const jti = randomBytes(16).toString('hex');
const accessToken = jwt.sign(
{ ...payload, jti, type: 'access' },
this.accessSecret,
{ expiresIn: this.accessExpiresIn, algorithm: 'HS256' }
);
const refreshToken = jwt.sign(
{ userId: payload.userId, jti, type: 'refresh' },
this.refreshSecret,
{ expiresIn: this.refreshExpiresIn, algorithm: 'HS256' }
);
return {
accessToken,
refreshToken,
expiresIn: this.accessExpiresIn,
};
}
verifyAccessToken(token: string): TokenPayload & { jti: string } {
try {
const decoded = jwt.verify(token, this.accessSecret) as JwtPayload & TokenPayload;
if (decoded.type !== 'access') {
throw new Error('Invalid token type');
}
return decoded as TokenPayload & { jti: string };
} catch (error) {
if (error instanceof jwt.TokenExpiredError) {
throw new Error('Token expired');
}
throw new Error('Invalid token');
}
}
verifyRefreshToken(token: string): { userId: string; jti: string } {
try {
const decoded = jwt.verify(token, this.refreshSecret) as JwtPayload;
if (decoded.type !== 'refresh') {
throw new Error('Invalid token type');
}
return { userId: decoded.userId, jti: decoded.jti };
} catch (error) {
throw new Error('Invalid refresh token');
}
}
// Fingerprint for token binding
generateFingerprint(): { fingerprint: string; hash: string } {
const fingerprint = randomBytes(32).toString('hex');
const hash = createHash('sha256').update(fingerprint).digest('hex');
return { fingerprint, hash };
}
}
Auth Middleware (Express)
// src/middleware/auth.middleware.ts
import { Request, Response, NextFunction } from 'express';
import { JwtService } from '../services/jwt.service';
declare global {
namespace Express {
interface Request {
user?: {
userId: string;
email: string;
roles: string[];
tenantId?: string;
};
}
}
}
const jwtService = new JwtService();
export function authenticate(req: Request, res: Response, next: NextFunction) {
const authHeader = req.headers.authorization;
if (!authHeader?.startsWith('Bearer ')) {
return res.status(401).json({ error: 'Missing authorization header' });
}
const token = authHeader.substring(7);
try {
const payload = jwtService.verifyAccessToken(token);
req.user = {
userId: payload.userId,
email: payload.email,
roles: payload.roles,
tenantId: payload.tenantId,
};
next();
} catch (error) {
const message = error instanceof Error ? error.message : 'Authentication failed';
return res.status(401).json({ error: message });
}
}
export function authorize(...allowedRoles: string[]) {
return (req: Request, res: Response, next: NextFunction) => {
if (!req.user) {
return res.status(401).json({ error: 'Not authenticated' });
}
const hasRole = req.user.roles.some(role => allowedRoles.includes(role));
if (!hasRole) {
return res.status(403).json({ error: 'Insufficient permissions' });
}
next();
};
}
// Permission-based authorization
export function requirePermission(permission: string) {
return async (req: Request, res: Response, next: NextFunction) => {
if (!req.user) {
return res.status(401).json({ error: 'Not authenticated' });
}
// Check permission in database
const hasPermission = await checkUserPermission(req.user.userId, permission);
if (!hasPermission) {
return res.status(403).json({ error: `Missing permission: ${permission}` });
}
next();
};
}
async function checkUserPermission(userId: string, permission: string): Promise<boolean> {
// Implementation depends on your permission model
// This is a placeholder
return true;
}
OAuth2 Integration
OAuth2 Provider (Passport.js)
// src/auth/oauth2.strategy.ts
import passport from 'passport';
import { Strategy as GoogleStrategy } from 'passport-google-oauth20';
import { Strategy as GitHubStrategy } from 'passport-github2';
import { UserService } from '../services/user.service';
const userService = new UserService();
// Google OAuth
passport.use(new GoogleStrategy({
clientID: process.env.GOOGLE_CLIENT_ID!,
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
callbackURL: '/auth/google/callback',
scope: ['profile', 'email'],
},
async (accessToken, refreshToken, profile, done) => {
try {
let user = await userService.findByProviderId('google', profile.id);
if (!user) {
user = await userService.createFromOAuth({
provider: 'google',
providerId: profile.id,
email: profile.emails?.[0]?.value,
name: profile.displayName,
avatar: profile.photos?.[0]?.value,
});
}
return done(null, user);
} catch (error) {
return done(error as Error);
}
}
));
// GitHub OAuth
passport.use(new GitHubStrategy({
clientID: process.env.GITHUB_CLIENT_ID!,
clientSecret: process.env.GITHUB_CLIENT_SECRET!,
callbackURL: '/auth/github/callback',
scope: ['user:email'],
},
async (accessToken, refreshToken, profile, done) => {
try {
let user = await userService.findByProviderId('github', profile.id);
if (!user) {
user = await userService.createFromOAuth({
provider: 'github',
providerId: profile.id,
email: profile.emails?.[0]?.value,
name: profile.displayName || profile.username,
avatar: profile.photos?.[0]?.value,
});
}
return done(null, user);
} catch (error) {
return done(error as Error);
}
}
));
// OAuth routes
import { Router } from 'express';
const router = Router();
router.get('/google', passport.authenticate('google'));
router.get('/google/callback',
passport.authenticate('google', { session: false, failureRedirect: '/login' }),
async (req, res) => {
const jwtService = new JwtService();
const user = req.user as any;
const tokens = jwtService.generateTokenPair({
userId: user.id,
email: user.email,
roles: user.roles,
});
// Set refresh token as HTTP-only cookie
res.cookie('refreshToken', tokens.refreshToken, {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'lax',
maxAge: 7 * 24 * 60 * 60 * 1000, // 7 days
});
// Redirect with access token
res.redirect(`/auth/success?token=${tokens.accessToken}`);
}
);
export { router as oauthRouter };
RBAC Implementation
Permission Model
// src/models/rbac.ts
interface Permission {
id: string;
name: string;
description: string;
resource: string;
action: 'create' | 'read' | 'update' | 'delete' | 'manage';
}
interface Role {
id: string;
name: string;
description: string;
permissions: Permission[];
inherits?: string[]; // Role inheritance
}
// Default roles
export const ROLES = {
ADMIN: {
id: 'admin',
name: 'Administrator',
description: 'Full system access',
permissions: [
{ id: 'manage:all', name: 'Manage All', resource: '*', action: 'manage' },
],
},
MANAGER: {
id: 'manager',
name: 'Manager',
description: 'Team management access',
permissions: [
{ id: 'read:users', name: 'Read Users', resource: 'users', action: 'read' },
{ id: 'manage:team', name: 'Manage Team', resource: 'team', action: 'manage' },
{ id: 'read:reports', name: 'Read Reports', resource: 'reports', action: 'read' },
],
},
USER: {
id: 'user',
name: 'User',
description: 'Standard user access',
permissions: [
{ id: 'read:own', name: 'Read Own Data', resource: 'own', action: 'read' },
{ id: 'update:own', name: 'Update Own Data', resource: 'own', action: 'update' },
],
},
} as const;
// RBAC Service
export class RbacService {
private roleHierarchy: Map<string, Set<string>> = new Map();
constructor() {
this.buildRoleHierarchy();
}
private buildRoleHierarchy(): void {
// Build role inheritance graph
Object.values(ROLES).forEach(role => {
const allPermissions = new Set<string>();
this.collectPermissions(role, allPermissions);
this.roleHierarchy.set(role.id, allPermissions);
});
}
private collectPermissions(role: Role, collected: Set<string>): void {
role.permissions.forEach(p => collected.add(`${p.resource}:${p.action}`));
role.inherits?.forEach(inheritedRole => {
const inherited = Object.values(ROLES).find(r => r.id === inheritedRole);
if (inherited) this.collectPermissions(inherited as Role, collected);
});
}
hasPermission(userRoles: string[], resource: string, action: string): boolean {
const requiredPermission = `${resource}:${action}`;
const wildcardPermission = '*:manage';
return userRoles.some(role => {
const permissions = this.roleHierarchy.get(role);
return permissions?.has(requiredPermission) || permissions?.has(wildcardPermission);
});
}
can(userRoles: string[], action: string, resource: string): boolean {
return this.hasPermission(userRoles, resource, action);
}
}
Attribute-Based Access Control (ABAC)
// src/services/abac.service.ts
interface Policy {
id: string;
effect: 'allow' | 'deny';
subjects: Record<string, unknown>;
resources: Record<string, unknown>;
actions: string[];
conditions?: Record<string, unknown>;
}
interface AccessRequest {
subject: {
userId: string;
roles: string[];
department?: string;
[key: string]: unknown;
};
resource: {
type: string;
id: string;
ownerId?: string;
[key: string]: unknown;
};
action: string;
environment: {
time: Date;
ip: string;
[key: string]: unknown;
};
}
export class AbacService {
private policies: Policy[] = [];
addPolicy(policy: Policy): void {
this.policies.push(policy);
}
evaluate(request: AccessRequest): 'allow' | 'deny' {
// Deny by default
let decision: 'allow' | 'deny' = 'deny';
for (const policy of this.policies) {
if (this.matchesPolicy(request, policy)) {
if (policy.effect === 'deny') {
return 'deny'; // Explicit deny takes precedence
}
decision = 'allow';
}
}
return decision;
}
private matchesPolicy(request: AccessRequest, policy: Policy): boolean {
// Match subjects
if (!this.matchAttributes(request.subject, policy.subjects)) return false;
// Match resources
if (!this.matchAttributes(request.resource, policy.resources)) return false;
// Match actions
if (!policy.actions.includes(request.action) && !policy.actions.includes('*')) {
return false;
}
// Evaluate conditions
if (policy.conditions && !this.evaluateConditions(request, policy.conditions)) {
return false;
}
return true;
}
private matchAttributes(obj: Record<string, unknown>, pattern: Record<string, unknown>): boolean {
return Object.entries(pattern).every(([key, value]) => {
if (value === '*') return true;
if (Array.isArray(value)) return value.includes(obj[key]);
return obj[key] === value;
});
}
private evaluateConditions(request: AccessRequest, conditions: Record<string, unknown>): boolean {
// Example: owner-only access
if (conditions.ownerOnly) {
return request.subject.userId === request.resource.ownerId;
}
// Example: time-based access
if (conditions.businessHours) {
const hour = request.environment.time.getHours();
return hour >= 9 && hour < 17;
}
return true;
}
}
// Usage example
const abac = new AbacService();
abac.addPolicy({
id: 'owner-crud',
effect: 'allow',
subjects: { roles: ['user'] },
resources: { type: 'document' },
actions: ['read', 'update', 'delete'],
conditions: { ownerOnly: true },
});
abac.addPolicy({
id: 'admin-all',
effect: 'allow',
subjects: { roles: ['admin'] },
resources: { type: '*' },
actions: ['*'],
});
Password Security
// src/services/password.service.ts
import argon2 from 'argon2';
import zxcvbn from 'zxcvbn';
interface PasswordStrength {
score: number;
feedback: string[];
crackTimeDisplay: string;
}
export class PasswordService {
private readonly minScore = 3; // 0-4 scale
async hash(password: string): Promise<string> {
return argon2.hash(password, {
type: argon2.argon2id,
memoryCost: 65536, // 64 MB
timeCost: 3,
parallelism: 4,
});
}
async verify(password: string, hash: string): Promise<boolean> {
try {
return await argon2.verify(hash, password);
} catch {
return false;
}
}
checkStrength(password: string, userInputs: string[] = []): PasswordStrength {
const result = zxcvbn(password, userInputs);
return {
score: result.score,
feedback: [
...(result.feedback.warning ? [result.feedback.warning] : []),
...result.feedback.suggestions,
],
crackTimeDisplay: result.crack_times_display.offline_slow_hashing_1e4_per_second as string,
};
}
validatePassword(password: string, userInputs: string[] = []): { valid: boolean; errors: string[] } {
const errors: string[] = [];
if (password.length < 12) {
errors.push('Password must be at least 12 characters');
}
const strength = this.checkStrength(password, userInputs);
if (strength.score < this.minScore) {
errors.push('Password is too weak');
errors.push(...strength.feedback);
}
return { valid: errors.length === 0, errors };
}
}
Usage Examples
Implement JWT Auth
Apply authentication-authorization skill to implement JWT authentication with refresh token rotation
Add OAuth2 Login
Apply authentication-authorization skill to add Google and GitHub OAuth login with account linking
Implement RBAC
Apply authentication-authorization skill to create role-based permissions for admin, manager, and user roles
Integration Points
- multi-tenant-security - Tenant-scoped authentication
- api-design-patterns - Auth endpoints design
- compliance-frameworks - SOC2/GDPR auth requirements
Success Output
When successful, this skill MUST output:
✅ SKILL COMPLETE: authentication-authorization
Completed:
- [x] JWT service with refresh token rotation implemented
- [x] OAuth2 integration (Google, GitHub) configured
- [x] RBAC with role hierarchy established
- [x] Password security (Argon2) implemented
- [x] Auth middleware and guards deployed
Outputs:
- src/services/jwt.service.ts
- src/middleware/auth.middleware.ts
- src/auth/oauth2.strategy.ts
- src/models/rbac.ts
- src/services/password.service.ts
- Auth endpoints: /auth/login, /auth/refresh, /auth/google, /auth/github
- Password strength: zxcvbn score ≥3 enforced
Completion Checklist
Before marking this skill as complete, verify:
- JWT access tokens expire in 15 minutes, refresh in 7 days
- OAuth2 providers tested and returning user profiles correctly
- RBAC roles (admin, manager, user) with correct permissions
- Password hashing uses Argon2id with secure parameters
- Auth middleware correctly extracts and validates JWT from headers
- Refresh token rotation working (old token invalidated)
- MFA support implemented (TOTP or WebAuthn)
Failure Indicators
This skill has FAILED if:
- ❌ JWT tokens don't expire or have incorrect lifetimes
- ❌ OAuth2 callback errors or doesn't create user accounts
- ❌ RBAC permissions incorrectly granted (privilege escalation)
- ❌ Passwords stored in plaintext or with weak hashing
- ❌ Auth middleware allows unauthenticated requests
- ❌ Refresh tokens reusable multiple times (no rotation)
When NOT to Use
Do NOT use this skill when:
- Using third-party auth services exclusively (Auth0, Firebase Auth, Clerk)
- Building internal tool with SSO-only access (no local auth needed)
- Prototype/MVP phase where auth can be mocked
- Stateless microservices relying on upstream auth gateway
- Single-user applications without authentication requirements
Use alternatives:
- Third-party auth platforms for reduced maintenance burden
- API gateway authentication for centralized auth in microservices
- Session-based auth for traditional server-rendered apps
- Simpler API key auth for service-to-service communication
Anti-Patterns (Avoid)
| Anti-Pattern | Problem | Solution |
|---|---|---|
| Weak password requirements | Easy to crack | Enforce min 12 chars, zxcvbn score ≥3 |
| Long-lived access tokens | Increased attack window | 15-min access, 7-day refresh tokens |
| No token rotation | Refresh token reuse | Invalidate old refresh token on use |
| Storing passwords in plaintext | Catastrophic breach | Use Argon2id with proper parameters |
| Missing rate limiting | Brute force attacks | Rate limit login attempts (5/min) |
| OAuth without state parameter | CSRF attacks | Always include state parameter |
| Hardcoded JWT secrets | Secrets in version control | Use environment variables |
Principles
This skill embodies:
- #4 Separation of Concerns - JWT service, OAuth, RBAC, password handling isolated
- #5 Eliminate Ambiguity - Clear role/permission model, explicit token lifetimes
- #6 Clear, Understandable, Explainable - Transparent permission checks, audit logs
- #7 Validate Continuously - Token expiry, password strength, permission checks
- #8 No Assumptions - Always verify tokens, never trust client-side auth state
- Security by Default - Secure defaults (HTTP-only cookies, short token lifetimes)
- Defense in Depth - Multiple layers (JWT, RBAC, MFA, password strength)