Skip to main content

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

  1. Review the patterns and examples below
  2. Apply the relevant patterns to your implementation
  3. Follow the best practices outlined in this skill

JWT, OAuth2, RBAC, session management, and secure authentication patterns for production applications.

Core Capabilities

  1. JWT Authentication - Stateless token-based auth with refresh tokens
  2. OAuth2 Integration - Social login, authorization code flow
  3. RBAC - Role-based access control with permissions
  4. Session Management - Secure cookie-based sessions
  5. 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-PatternProblemSolution
Weak password requirementsEasy to crackEnforce min 12 chars, zxcvbn score ≥3
Long-lived access tokensIncreased attack window15-min access, 7-day refresh tokens
No token rotationRefresh token reuseInvalidate old refresh token on use
Storing passwords in plaintextCatastrophic breachUse Argon2id with proper parameters
Missing rate limitingBrute force attacksRate limit login attempts (5/min)
OAuth without state parameterCSRF attacksAlways include state parameter
Hardcoded JWT secretsSecrets in version controlUse 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)