User Authentication

User Authentication Example#

This example demonstrates a complete spec-driven workflow for building JWT-based user authentication. It covers login, signup, password reset, and security best practices.

Phase 1: Initialize#

/spec-init user-authentication

Initial description:

JWT-based user authentication system with email/password login, user registration, password reset, and protected routes. Must include rate limiting and secure password storage.

Generated spec structure:

.specs/user-authentication/
├── spec.json
└── requirements.md (template)

Phase 2: Requirements#

Running /spec-requirements user-authentication generates:

# User Authentication - Requirements

## Purpose
Implement secure user authentication with JWT tokens to protect API endpoints
and enable personalized user experiences.

## Target Users
- End users accessing the application
- System administrators managing user accounts

## Functional Requirements

### FR-1: User Registration
When a new user provides valid email and password,
the system shall create an account and return a JWT token
within 500ms.

**Acceptance Criteria:**
- Email must be unique
- Password must be 8+ characters with uppercase, lowercase, and number
- Returns JWT token on success
- Sends welcome email

### FR-2: User Login
When a registered user provides correct credentials,
the system shall authenticate and return a JWT token
with 24-hour expiration.

**Acceptance Criteria:**
- Validates email exists
- Verifies password with bcrypt
- Returns JWT token on success
- Returns generic error on failure (security)

### FR-3: Password Reset
When a user requests password reset,
the system shall send a secure reset link valid for 1 hour.

**Acceptance Criteria:**
- Reset link contains secure token
- Token expires after 1 hour
- Old password remains valid until reset
- Email sent asynchronously

### FR-4: Protected Routes
When accessing protected endpoints without valid JWT,
the system shall reject the request with 401 status.

**Acceptance Criteria:**
- Validates JWT signature
- Checks token expiration
- Extracts user ID from token
- Returns 401 for invalid/missing tokens

## Non-Functional Requirements

### Security
- Passwords hashed with bcrypt (cost factor 12)
- JWT secret minimum 256 bits
- Rate limiting: 5 login attempts per 15 minutes per IP
- HTTPS only in production

### Performance
- Login response time < 500ms
- Token validation < 10ms
- Support 1000 concurrent users

### Compliance
- GDPR compliant (data deletion)
- Audit logs for authentication events

Phase 3: Design#

Running /spec-design user-authentication generates:

# User Authentication - Design

## Architecture

┌─────────────┐ ┌──────────────┐ ┌─────────────┐ │ Client │─────▶│ Auth API │─────▶│ Database │ └─────────────┘ └──────────────┘ └─────────────┘ │ ▼ ┌──────────────┐ │ JWT Tokens │ └──────────────┘


## API Design

### POST /api/auth/register

**Request:**
```json
{
  "email": "user@example.com",
  "password": "SecurePass123"
}

Response (201):

{
  "token": "eyJhbGciOiJIUzI1NiIs...",
  "user": {
    "id": "uuid",
    "email": "user@example.com"
  }
}

Response (400):

{
  "error": "Email already exists"
}

POST /api/auth/login#

Request:

{
  "email": "user@example.com",
  "password": "SecurePass123"
}

Response (200):

{
  "token": "eyJhbGciOiJIUzI1NiIs...",
  "user": {
    "id": "uuid",
    "email": "user@example.com"
  }
}

Response (401):

{
  "error": "Invalid credentials"
}

POST /api/auth/forgot-password#

Request:

{
  "email": "user@example.com"
}

Response (200):

{
  "message": "Reset email sent"
}

Database Schema#

-- Users table
CREATE TABLE users (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  email VARCHAR(255) UNIQUE NOT NULL,
  password_hash VARCHAR(255) NOT NULL,
  created_at TIMESTAMP DEFAULT NOW(),
  updated_at TIMESTAMP DEFAULT NOW()
);

-- Password reset tokens
CREATE TABLE password_resets (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  user_id UUID REFERENCES users(id) ON DELETE CASCADE,
  token_hash VARCHAR(255) NOT NULL,
  expires_at TIMESTAMP NOT NULL,
  created_at TIMESTAMP DEFAULT NOW()
);

-- Indexes
CREATE INDEX idx_users_email ON users(email);
CREATE INDEX idx_password_resets_token ON password_resets(token_hash);

Security Measures#

  1. Password Hashing

    • bcrypt with cost factor 12
    • Never store plain text passwords
  2. JWT Configuration

    • HS256 algorithm
    • 24-hour expiration
    • Secure secret from environment
  3. Rate Limiting

    • 5 attempts per 15 minutes
    • IP-based tracking
    • Returns 429 status when exceeded
  4. Input Validation

    • Email format validation
    • Password strength requirements
    • SQL injection prevention (parameterized queries)

Error Handling#

ScenarioStatusMessage
Invalid email format400"Invalid email format"
Weak password400"Password does not meet requirements"
Email exists400"Email already registered"
Invalid credentials401"Invalid credentials"
Missing token401"Authentication required"
Expired token401"Token expired"
Rate limited429"Too many attempts"

---

## Phase 4: Tasks

Running `/spec-tasks user-authentication` generates:

```markdown
# User Authentication - Sprint Plan

**Estimated Time:** 6 hours

## TASK 1: Database Setup (45 min)
**Priority:** P0 (Blocker)
**Dependencies:** None

**Input:** Design document schema

**OUTPUT:**
- Migration files for users and password_resets tables
- Indexes created
- Database connection configured

**VERIFY:**
```bash
npm run db:migrate
# Check tables exist

TASK 2: Password Hashing Utility (30 min)#

Priority: P0 (Blocker) Dependencies: None

OUTPUT:

  • src/lib/password.ts with hashPassword() and verifyPassword()
  • Unit tests

VERIFY:

npm test src/lib/password.test.ts

TASK 3: JWT Utility (30 min)#

Priority: P0 (Blocker) Dependencies: None

OUTPUT:

  • src/lib/jwt.ts with signToken() and verifyToken()
  • Unit tests

VERIFY:

npm test src/lib/jwt.test.ts

TASK 4: Register Endpoint (1 hour)#

Priority: P0 (Blocker) Dependencies: TASK 1, TASK 2

OUTPUT:

  • src/app/api/auth/register/route.ts
  • Input validation
  • Password hashing
  • JWT generation
  • Error handling

VERIFY:

curl -X POST http://localhost:3000/api/auth/register \
  -H "Content-Type: application/json" \
  -d '{"email":"test@example.com","password":"TestPass123"}'
# Returns 201 with token

TASK 5: Login Endpoint (45 min)#

Priority: P0 (Blocker) Dependencies: TASK 1, TASK 2, TASK 3

OUTPUT:

  • src/app/api/auth/login/route.ts
  • Credential validation
  • Rate limiting middleware
  • JWT generation

VERIFY:

curl -X POST http://localhost:3000/api/auth/login \
  -H "Content-Type: application/json" \
  -d '{"email":"test@example.com","password":"TestPass123"}'
# Returns 200 with token

TASK 6: Forgot Password Endpoint (1 hour)#

Priority: P1 (High) Dependencies: TASK 1, TASK 3

OUTPUT:

  • src/app/api/auth/forgot-password/route.ts
  • Token generation and storage
  • Email service integration
  • Expiration handling

VERIFY:

  • Check email sent (use test email catcher)
  • Verify token stored in database

TASK 7: Auth Middleware (45 min)#

Priority: P0 (Blocker) Dependencies: TASK 3

OUTPUT:

  • src/middleware/auth.ts
  • JWT verification
  • User extraction
  • 401 responses for invalid tokens

VERIFY:

  • Test with valid token → passes
  • Test with invalid token → 401

TASK 8: Protected Route Example (30 min)#

Priority: P1 (High) Dependencies: TASK 7

OUTPUT:

  • src/app/api/user/profile/route.ts
  • Demonstrates auth middleware usage
  • Returns user data from token

TASK 9: Integration Tests (1 hour)#

Priority: P1 (High) Dependencies: All above tasks

OUTPUT:

  • src/tests/auth.integration.test.ts
  • Register → Login → Access protected route flow
  • Error case testing

VERIFY:

npm run test:integration

---

## Phase 5: Implementation

Run `/code user-authentication` to implement the next pending task, then `/test` and `/review`.

### Key Implementation Details

#### Password Hashing (`src/lib/password.ts`)

```typescript
import bcrypt from 'bcrypt';

const SALT_ROUNDS = 12;

export async function hashPassword(password: string): Promise<string> {
  return bcrypt.hash(password, SALT_ROUNDS);
}

export async function verifyPassword(
  password: string,
  hash: string
): Promise<boolean> {
  return bcrypt.compare(password, hash);
}

JWT Utility (src/lib/jwt.ts)#

import jwt from 'jsonwebtoken';

const JWT_SECRET = process.env.JWT_SECRET!;
const JWT_EXPIRES_IN = '24h';

export function signToken(userId: string): string {
  return jwt.sign({ userId }, JWT_SECRET, {
    expiresIn: JWT_EXPIRES_IN,
  });
}

export function verifyToken(token: string): { userId: string } {
  return jwt.verify(token, JWT_SECRET) as { userId: string };
}

Auth Middleware (src/middleware/auth.ts)#

import { NextRequest, NextResponse } from 'next/server';
import { verifyToken } from '@/lib/jwt';

export function withAuth(handler: Function) {
  return async (req: NextRequest) => {
    const token = req.headers.get('authorization')?.replace('Bearer ', '');

    if (!token) {
      return NextResponse.json(
        { error: 'Authentication required' },
        { status: 401 }
      );
    }

    try {
      const payload = verifyToken(token);
      req.userId = payload.userId;
      return handler(req);
    } catch {
      return NextResponse.json(
        { error: 'Invalid token' },
        { status: 401 }
      );
    }
  };
}

Phase 6: Status#

Running /spec-status user-authentication shows:

# User Authentication - Status Report

Generated: 2026-02-02 14:30

## Progress: 100% (9/9 tasks complete)

### Completed Tasks ✅
- TASK 1: Database Setup (45 min)
- TASK 2: Password Hashing Utility (30 min)
- TASK 3: JWT Utility (30 min)
- TASK 4: Register Endpoint (1 hour)
- TASK 5: Login Endpoint (45 min)
- TASK 6: Forgot Password Endpoint (1 hour)
- TASK 7: Auth Middleware (45 min)
- TASK 8: Protected Route Example (30 min)
- TASK 9: Integration Tests (1 hour)

### Blockers: None

### Total Time: 6 hours 15 minutes

### Next Steps:
- Deploy to staging
- Perform security audit
- Update API documentation

Key Takeaways#

What Went Well#

  • Clear requirements prevented scope creep
  • Design phase caught security issues early
  • Task breakdown made estimation accurate
  • Tests caught integration issues

Lessons Learned#

  • Rate limiting should be implemented early (moved to TASK 5)
  • Email service needed mock for testing
  • JWT secret rotation process needed documentation

Security Checklist#

  • ✅ Passwords hashed with bcrypt
  • ✅ JWT tokens expire in 24 hours
  • ✅ Rate limiting on login
  • ✅ Input validation on all endpoints
  • ✅ Generic error messages for security
  • ✅ HTTPS enforcement in production

Want to try this yourself? Run the commands above in your own project!