Authentication Setup
When to use this skill
Lists specific situations where this skill should be triggered:
-
User Login System: When adding user authentication to a new application
-
API Security: When adding an authentication layer to a REST or GraphQL API
-
Permission Management: When role-based access control is needed
-
Authentication Migration: When migrating an existing auth system to JWT or OAuth
-
SSO Integration: When integrating social login with Google, GitHub, Microsoft, etc.
Input Format
The required and optional input information to collect from the user:
Required Information
-
Authentication Method: Choose from JWT, Session, or OAuth 2.0
-
Backend Framework: Express, Django, FastAPI, Spring Boot, etc.
-
Database: PostgreSQL, MySQL, MongoDB, etc.
-
Security Requirements: Password policy, token expiry times, etc.
Optional Information
-
MFA Support: Whether to enable 2FA/MFA (default: false)
-
Social Login: OAuth providers (Google, GitHub, etc.)
-
Session Storage: Redis, in-memory, etc. (if using sessions)
-
Refresh Token: Whether to use (default: true)
Input Example
Build a user authentication system:
- Auth method: JWT
- Framework: Express.js + TypeScript
- Database: PostgreSQL
- MFA: Google Authenticator support
- Social login: Google, GitHub
- Refresh Token: enabled
Instructions
Specifies the step-by-step task sequence to follow precisely.
Step 1: Design the Data Model
Design the database schema for users and authentication.
Tasks:
-
Design the User table (id, email, password_hash, role, created_at, updated_at)
-
RefreshToken table (optional)
-
OAuthProvider table (if using social login)
-
Never store passwords in plaintext (bcrypt/argon2 hashing is mandatory)
Example (PostgreSQL):
CREATE TABLE users ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), email VARCHAR(255) UNIQUE NOT NULL, password_hash VARCHAR(255), -- NULL if OAuth only role VARCHAR(50) DEFAULT 'user', is_verified BOOLEAN DEFAULT false, mfa_secret VARCHAR(255), created_at TIMESTAMP DEFAULT NOW(), updated_at TIMESTAMP DEFAULT NOW() );
CREATE TABLE refresh_tokens ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), user_id UUID REFERENCES users(id) ON DELETE CASCADE, token VARCHAR(500) UNIQUE NOT NULL, expires_at TIMESTAMP NOT NULL, created_at TIMESTAMP DEFAULT NOW() );
CREATE INDEX idx_users_email ON users(email); CREATE INDEX idx_refresh_tokens_user_id ON refresh_tokens(user_id);
Step 2: Implement Password Security
Implement password hashing and verification logic.
Tasks:
-
Use bcrypt (Node.js) or argon2 (Python)
-
Set salt rounds to a minimum of 10
-
Password strength validation (minimum 8 chars, upper/lowercase, numbers, special characters)
Decision Criteria:
-
Node.js projects → use the bcrypt library
-
Python projects → use argon2-cffi or passlib
-
Performance-critical cases → choose bcrypt
-
Cases requiring maximum security → choose argon2
Example (Node.js + TypeScript):
import bcrypt from 'bcrypt';
const SALT_ROUNDS = 12;
export async function hashPassword(password: string): Promise<string> { // Validate password strength if (password.length < 8) { throw new Error('Password must be at least 8 characters'); }
const hasUpperCase = /[A-Z]/.test(password);
const hasLowerCase = /[a-z]/.test(password);
const hasNumber = /\d/.test(password);
const hasSpecial = /[!@#$%^&*(),.?":{}|<>]/.test(password);
if (!hasUpperCase || !hasLowerCase || !hasNumber || !hasSpecial) {
throw new Error('Password must contain uppercase, lowercase, number, and special character');
}
return await bcrypt.hash(password, SALT_ROUNDS);
}
export async function verifyPassword(password: string, hash: string): Promise<boolean> { return await bcrypt.compare(password, hash); }
Step 3: Generate and Verify JWT Tokens
Implement a token system for JWT-based authentication.
Tasks:
-
Access Token (short expiry: 15 minutes)
-
Refresh Token (long expiry: 7–30 days)
-
Use a strong SECRET key for JWT signing (manage via environment variables)
-
Include only the minimum necessary information in the token payload (user_id, role)
Example (Node.js):
import jwt from 'jsonwebtoken';
const ACCESS_TOKEN_SECRET = process.env.ACCESS_TOKEN_SECRET!; const REFRESH_TOKEN_SECRET = process.env.REFRESH_TOKEN_SECRET!; const ACCESS_TOKEN_EXPIRY = '15m'; const REFRESH_TOKEN_EXPIRY = '7d';
interface TokenPayload { userId: string; email: string; role: string; }
export function generateAccessToken(payload: TokenPayload): string { return jwt.sign(payload, ACCESS_TOKEN_SECRET, { expiresIn: ACCESS_TOKEN_EXPIRY, issuer: 'your-app-name', audience: 'your-app-users' }); }
export function generateRefreshToken(payload: TokenPayload): string { return jwt.sign(payload, REFRESH_TOKEN_SECRET, { expiresIn: REFRESH_TOKEN_EXPIRY, issuer: 'your-app-name', audience: 'your-app-users' }); }
export function verifyAccessToken(token: string): TokenPayload { return jwt.verify(token, ACCESS_TOKEN_SECRET, { issuer: 'your-app-name', audience: 'your-app-users' }) as TokenPayload; }
export function verifyRefreshToken(token: string): TokenPayload { return jwt.verify(token, REFRESH_TOKEN_SECRET, { issuer: 'your-app-name', audience: 'your-app-users' }) as TokenPayload; }
Step 4: Implement Authentication Middleware
Write authentication middleware to protect API requests.
Checklist:
-
Extract Bearer token from the Authorization header
-
Verify token and check expiry
-
Attach user info to req.user for valid tokens
-
Error handling (401 Unauthorized)
Example (Express.js):
import { Request, Response, NextFunction } from 'express'; import { verifyAccessToken } from './jwt';
export interface AuthRequest extends Request { user?: { userId: string; email: string; role: string; }; }
export function authenticateToken(req: AuthRequest, res: Response, next: NextFunction) { const authHeader = req.headers['authorization']; const token = authHeader && authHeader.split(' ')[1]; // Bearer TOKEN
if (!token) {
return res.status(401).json({ error: 'Access token required' });
}
try {
const payload = verifyAccessToken(token);
req.user = payload;
next();
} catch (error) {
if (error.name === 'TokenExpiredError') {
return res.status(401).json({ error: 'Token expired' });
}
return res.status(403).json({ error: 'Invalid token' });
}
}
// Role-based authorization middleware export function requireRole(...roles: string[]) { return (req: AuthRequest, res: Response, next: NextFunction) => { if (!req.user) { return res.status(401).json({ error: 'Authentication required' }); }
if (!roles.includes(req.user.role)) {
return res.status(403).json({ error: 'Insufficient permissions' });
}
next();
};
}
Step 5: Implement Authentication API Endpoints
Write APIs for registration, login, token refresh, etc.
Tasks:
-
POST /auth/register - registration
-
POST /auth/login - login
-
POST /auth/refresh - token refresh
-
POST /auth/logout - logout
-
GET /auth/me - current user info
Example:
import express from 'express'; import { hashPassword, verifyPassword } from './password'; import { generateAccessToken, generateRefreshToken, verifyRefreshToken } from './jwt'; import { authenticateToken } from './middleware';
const router = express.Router();
// Registration router.post('/register', async (req, res) => { try { const { email, password } = req.body;
// Check for duplicate email
const existingUser = await db.user.findUnique({ where: { email } });
if (existingUser) {
return res.status(409).json({ error: 'Email already exists' });
}
// Hash the password
const passwordHash = await hashPassword(password);
// Create the user
const user = await db.user.create({
data: { email, password_hash: passwordHash, role: 'user' }
});
// Generate tokens
const accessToken = generateAccessToken({
userId: user.id,
email: user.email,
role: user.role
});
const refreshToken = generateRefreshToken({
userId: user.id,
email: user.email,
role: user.role
});
// Store Refresh token in DB
await db.refreshToken.create({
data: {
user_id: user.id,
token: refreshToken,
expires_at: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000) // 7 days
}
});
res.status(201).json({
user: { id: user.id, email: user.email, role: user.role },
accessToken,
refreshToken
});
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// Login router.post('/login', async (req, res) => { try { const { email, password } = req.body;
// Find the user
const user = await db.user.findUnique({ where: { email } });
if (!user || !user.password_hash) {
return res.status(401).json({ error: 'Invalid credentials' });
}
// Verify the password
const isValid = await verifyPassword(password, user.password_hash);
if (!isValid) {
return res.status(401).json({ error: 'Invalid credentials' });
}
// Generate tokens
const accessToken = generateAccessToken({
userId: user.id,
email: user.email,
role: user.role
});
const refreshToken = generateRefreshToken({
userId: user.id,
email: user.email,
role: user.role
});
// Store Refresh token
await db.refreshToken.create({
data: {
user_id: user.id,
token: refreshToken,
expires_at: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000)
}
});
res.json({
user: { id: user.id, email: user.email, role: user.role },
accessToken,
refreshToken
});
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// Token refresh router.post('/refresh', async (req, res) => { try { const { refreshToken } = req.body;
if (!refreshToken) {
return res.status(401).json({ error: 'Refresh token required' });
}
// Verify Refresh token
const payload = verifyRefreshToken(refreshToken);
// Check token in DB
const storedToken = await db.refreshToken.findUnique({
where: { token: refreshToken }
});
if (!storedToken || storedToken.expires_at < new Date()) {
return res.status(403).json({ error: 'Invalid or expired refresh token' });
}
// Generate new Access token
const accessToken = generateAccessToken({
userId: payload.userId,
email: payload.email,
role: payload.role
});
res.json({ accessToken });
} catch (error) {
res.status(403).json({ error: 'Invalid refresh token' });
}
});
// Current user info router.get('/me', authenticateToken, async (req: AuthRequest, res) => { try { const user = await db.user.findUnique({ where: { id: req.user!.userId }, select: { id: true, email: true, role: true, created_at: true } });
res.json({ user });
} catch (error) {
res.status(500).json({ error: error.message });
}
});
export default router;
Output format
Defines the exact format that deliverables should follow.
Basic Structure
Project directory/ ├── src/ │ ├── auth/ │ │ ├── password.ts # password hashing/verification │ │ ├── jwt.ts # JWT token generation/verification │ │ ├── middleware.ts # authentication middleware │ │ └── routes.ts # authentication API endpoints │ ├── models/ │ │ └── User.ts # user model │ └── database/ │ └── schema.sql # database schema ├── .env.example # environment variable template └── README.md # authentication system documentation
Environment Variable File (.env.example)
JWT Secrets (MUST change in production)
ACCESS_TOKEN_SECRET=your-access-token-secret-min-32-characters REFRESH_TOKEN_SECRET=your-refresh-token-secret-min-32-characters
Database
DATABASE_URL=postgresql://user:password@localhost:5432/myapp
OAuth (Optional)
GOOGLE_CLIENT_ID=your-google-client-id GOOGLE_CLIENT_SECRET=your-google-client-secret GITHUB_CLIENT_ID=your-github-client-id GITHUB_CLIENT_SECRET=your-github-client-secret
Constraints
Specifies mandatory rules and prohibited actions.
Mandatory Rules (MUST)
Password Security: Never store passwords in plaintext
-
Use a proven hashing algorithm such as bcrypt or argon2
-
Salt rounds minimum of 10
Environment Variable Management: Manage all secret keys via environment variables
-
Add .env files to .gitignore
-
Provide a list of required variables via .env.example
Token Expiry: Access Tokens should be short-lived (15 min), Refresh Tokens appropriately longer (7 days)
-
Balance security and user experience
-
Store Refresh Tokens in the DB to enable revocation
Prohibited Actions (MUST NOT)
Plaintext Passwords: Never store passwords in plaintext or print them to logs
-
Serious security risk
-
Legal liability issues
Hardcoding JWT SECRET: Do not write SECRET keys directly in code
-
Risk of being exposed on GitHub
-
Production security vulnerability
Sensitive Data in Tokens: Do not include passwords, card numbers, or other sensitive data in JWT payloads
-
JWT can be decoded (it is not encrypted)
-
Include only the minimum information (user_id, role)
Security Rules
-
Rate Limiting: Apply rate limiting to the login API (prevents brute-force attacks)
-
HTTPS Required: Use HTTPS only in production environments
-
CORS Configuration: Allow only approved domains to access the API
-
Input Validation: Validate all user input (prevents SQL Injection and XSS)
Examples
Demonstrates how to apply the skill through real-world use cases.
Example 1: Express.js + PostgreSQL JWT Authentication
Situation: Adding JWT-based user authentication to a Node.js Express app
User Request:
Add JWT authentication to an Express.js app using PostgreSQL, with access token expiry of 15 minutes and refresh token expiry of 7 days.
Skill Application Process:
Install packages:
npm install jsonwebtoken bcrypt pg npm install --save-dev @types/jsonwebtoken @types/bcrypt
Create the database schema (use the SQL above)
Set environment variables:
ACCESS_TOKEN_SECRET=$(openssl rand -base64 32) REFRESH_TOKEN_SECRET=$(openssl rand -base64 32)
Implement auth modules (use the code examples above)
Connect API routes:
import authRoutes from './auth/routes'; app.use('/api/auth', authRoutes);
Final Result: JWT-based authentication system complete, registration/login/token-refresh APIs working
Example 2: Role-Based Access Control (RBAC)
Situation: A permission system that distinguishes administrators from regular users
User Request:
Create an API accessible only to administrators. Regular users should receive a 403 error.
Final Result:
// Admin-only API router.delete('/users/:id', authenticateToken, // verify authentication requireRole('admin'), // verify role async (req, res) => { // user deletion logic await db.user.delete({ where: { id: req.params.id } }); res.json({ message: 'User deleted' }); } );
// Usage example // Regular user (role: 'user') request → 403 Forbidden // Admin (role: 'admin') request → 200 OK
Best practices
Recommendations for using this skill effectively.
Quality Improvement
Password Rotation Policy: Recommend periodic password changes
-
Change notification every 90 days
-
Prevent reuse of the last 5 passwords
-
Balance user experience and security
Multi-Factor Authentication (MFA): Apply 2FA to important accounts
-
Use TOTP apps such as Google Authenticator or Authy
-
SMS is less secure (risk of SIM swapping)
-
Provide backup codes
Audit Logging: Log all authentication events
-
Record login success/failure, IP address, and User Agent
-
Anomaly detection and post-incident analysis
-
GDPR compliance (exclude sensitive data)
Efficiency Improvements
-
Token Blacklist: Revoke Refresh Tokens on logout
-
Redis Caching: Cache frequently used user data
-
Database Indexing: Add indexes on email and refresh_token
Common Issues
Common problems and their solutions.
Issue 1: "JsonWebTokenError: invalid signature"
Symptom:
-
Error occurs during token verification
-
Login succeeds but authenticated API calls fail
Cause: The SECRET keys for Access Token and Refresh Token are different, but the same key is being used to verify both.
Solution:
-
Check environment variables: ACCESS_TOKEN_SECRET , REFRESH_TOKEN_SECRET
-
Use the correct SECRET for each token type
-
Verify that environment variables load correctly (initialize dotenv )
Issue 2: Frontend Cannot Log In Due to CORS Error
Symptom: "CORS policy" error in the browser console
Cause: Missing CORS configuration on the Express server
Solution:
import cors from 'cors';
app.use(cors({ origin: process.env.FRONTEND_URL || 'http://localhost:3000', credentials: true }));
Issue 3: Refresh Token Keeps Expiring
Symptom: Users are frequently logged out
Cause: Refresh Token is not properly managed in the DB
Solution:
-
Confirm Refresh Token is saved to DB upon creation
-
Set an appropriate expiry time (minimum 7 days)
-
Add a cron job to regularly clean up expired tokens
References
Official Documentation
-
JWT.io - JSON Web Token Introduction
-
OWASP Authentication Cheat Sheet
-
OAuth 2.0 RFC 6749
Libraries
-
jsonwebtoken (Node.js)
-
bcrypt (Node.js)
-
Passport.js - multiple authentication strategies
-
NextAuth.js - Next.js authentication
Security Guides
-
OWASP Top 10
-
NIST Digital Identity Guidelines
Metadata
Version
-
Current Version: 1.0.0
-
Last Updated: 2025-01-01
-
Compatible Platforms: Claude, ChatGPT, Gemini
Related Skills
-
api-design: API endpoint design
-
security: Security best practices
Tags
#authentication #authorization #JWT #OAuth #security #backend