Spaces:
Sleeping
Sleeping
| # Research: Authentication & API Security | |
| **Feature**: 001-auth-security | |
| **Date**: 2026-01-09 | |
| **Phase**: 0 - Research & Technical Decisions | |
| ## Overview | |
| This document captures research findings and technical decisions for implementing authentication and API security using Better Auth (frontend) and JWT verification (backend). | |
| ## Research Questions & Resolutions | |
| ### 1. Token Expiry Duration | |
| **Question**: Spec says 1 hour, user input says 7 days - which should we use? | |
| **Decision**: **7 days** | |
| **Rationale**: | |
| - The spec explicitly excludes "Token refresh mechanism and refresh tokens" from scope | |
| - Without refresh tokens, 1-hour expiry creates poor UX (users logged out every hour) | |
| - This is a hackathon/MVP project where simplicity is prioritized | |
| - 7 days balances security with usability for the initial release | |
| - Industry standard for web apps *with refresh tokens* is 1 hour access + long-lived refresh | |
| - Industry standard for web apps *without refresh tokens* is 7-30 days | |
| **Alternatives Considered**: | |
| - 1 hour: Too short without refresh mechanism, poor UX | |
| - 24 hours: Reasonable middle ground, but 7 days is acceptable for MVP | |
| - 30 days: Too long, increases security risk unnecessarily | |
| **Implementation**: Set `exp` claim in JWT to 7 days (604800 seconds) from issuance | |
| --- | |
| ### 2. Better Auth Integration Pattern | |
| **Question**: How should Better Auth be integrated in Next.js 16 App Router? | |
| **Decision**: Use Better Auth with email/password provider and JWT plugin | |
| **Research Findings**: | |
| - Better Auth supports Next.js App Router with server-side session management | |
| - JWT plugin allows issuing tokens that can be verified by external backends | |
| - Configuration file: `lib/auth.ts` with email provider and JWT plugin | |
| - Session management via Better Auth's built-in session handling | |
| - Token accessible via `auth()` helper in server components | |
| **Implementation Pattern**: | |
| ```typescript | |
| // lib/auth.ts | |
| import { betterAuth } from "better-auth" | |
| import { jwt } from "better-auth/plugins" | |
| export const auth = betterAuth({ | |
| database: { | |
| // Database connection for Better Auth's session storage | |
| }, | |
| emailAndPassword: { | |
| enabled: true, | |
| }, | |
| plugins: [ | |
| jwt({ | |
| secret: process.env.BETTER_AUTH_SECRET!, | |
| expiresIn: "7d", | |
| }) | |
| ], | |
| }) | |
| ``` | |
| **Alternatives Considered**: | |
| - NextAuth.js: More popular but heavier, Better Auth is simpler for JWT use case | |
| - Custom JWT implementation: Reinventing the wheel, Better Auth handles edge cases | |
| - Auth0/Clerk: Third-party services, adds external dependency and cost | |
| --- | |
| ### 3. Backend JWT Verification Strategy | |
| **Question**: How should FastAPI verify JWT tokens from Better Auth? | |
| **Decision**: Use PyJWT library with FastAPI dependency injection | |
| **Research Findings**: | |
| - PyJWT is the standard Python library for JWT handling | |
| - FastAPI's dependency injection system is ideal for auth middleware | |
| - Better Auth uses HS256 (HMAC-SHA256) by default with shared secret | |
| - Token verification should happen in a reusable dependency | |
| **Implementation Pattern**: | |
| ```python | |
| # src/core/security.py | |
| import jwt | |
| from fastapi import HTTPException, status | |
| from src.core.config import settings | |
| def verify_jwt_token(token: str) -> dict: | |
| try: | |
| payload = jwt.decode( | |
| token, | |
| settings.BETTER_AUTH_SECRET, | |
| algorithms=["HS256"] | |
| ) | |
| return payload | |
| except jwt.ExpiredSignatureError: | |
| raise HTTPException( | |
| status_code=status.HTTP_401_UNAUTHORIZED, | |
| detail="Token has expired" | |
| ) | |
| except jwt.InvalidTokenError: | |
| raise HTTPException( | |
| status_code=status.HTTP_401_UNAUTHORIZED, | |
| detail="Invalid token" | |
| ) | |
| # src/api/deps.py | |
| from fastapi import Depends, HTTPException, status | |
| from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials | |
| security = HTTPBearer() | |
| def get_current_user( | |
| credentials: HTTPAuthorizationCredentials = Depends(security) | |
| ) -> int: | |
| token = credentials.credentials | |
| payload = verify_jwt_token(token) | |
| user_id = payload.get("sub") # Better Auth uses 'sub' for user ID | |
| if not user_id: | |
| raise HTTPException( | |
| status_code=status.HTTP_401_UNAUTHORIZED, | |
| detail="Invalid token payload" | |
| ) | |
| return int(user_id) | |
| ``` | |
| **Alternatives Considered**: | |
| - python-jose: Older library, PyJWT is more actively maintained | |
| - Middleware approach: Less flexible than dependency injection | |
| - Manual token parsing: Error-prone, PyJWT handles edge cases | |
| --- | |
| ### 4. Password Hashing Strategy | |
| **Question**: How should passwords be hashed and verified? | |
| **Decision**: Use passlib with bcrypt algorithm | |
| **Research Findings**: | |
| - Better Auth handles password hashing on the frontend side | |
| - Backend needs to verify passwords for custom auth endpoints (if any) | |
| - passlib is the standard Python library for password hashing | |
| - bcrypt is industry-standard, resistant to rainbow table attacks | |
| - Cost factor of 12 provides good security/performance balance | |
| **Implementation Pattern**: | |
| ```python | |
| # src/core/security.py | |
| from passlib.context import CryptContext | |
| pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") | |
| def hash_password(password: str) -> str: | |
| return pwd_context.hash(password) | |
| def verify_password(plain_password: str, hashed_password: str) -> bool: | |
| return pwd_context.verify(plain_password, hashed_password) | |
| ``` | |
| **Note**: Since Better Auth handles authentication, backend password hashing may only be needed for: | |
| - Admin user creation scripts | |
| - Testing utilities | |
| - Future direct authentication endpoints | |
| **Alternatives Considered**: | |
| - argon2: More modern but requires C dependencies, complicates deployment | |
| - scrypt: Good but bcrypt is more widely supported | |
| - Plain SHA256: Insecure, vulnerable to rainbow tables | |
| --- | |
| ### 5. Frontend Token Storage | |
| **Question**: Where should JWT tokens be stored in the frontend? | |
| **Decision**: Use Better Auth's built-in session management (httpOnly cookies) | |
| **Research Findings**: | |
| - Better Auth stores session tokens in httpOnly cookies by default | |
| - This prevents XSS attacks (JavaScript cannot access the token) | |
| - Better Auth's `auth()` helper automatically includes token in requests | |
| - For API calls to backend, extract token from Better Auth session | |
| **Implementation Pattern**: | |
| ```typescript | |
| // lib/api.ts | |
| import { auth } from './auth' | |
| async function fetchAPI<T>(endpoint: string, options: RequestInit = {}): Promise<T> { | |
| const session = await auth() | |
| const token = session?.token // Better Auth provides token in session | |
| const response = await fetch(`${API_BASE_URL}${endpoint}`, { | |
| ...options, | |
| headers: { | |
| 'Content-Type': 'application/json', | |
| ...(token && { 'Authorization': `Bearer ${token}` }), | |
| ...options.headers, | |
| }, | |
| }) | |
| // Handle 401 responses | |
| if (response.status === 401) { | |
| // Redirect to login | |
| window.location.href = '/auth/signin' | |
| } | |
| return response.json() | |
| } | |
| ``` | |
| **Alternatives Considered**: | |
| - localStorage: Vulnerable to XSS attacks | |
| - sessionStorage: Same XSS vulnerability as localStorage | |
| - Memory only: Lost on page refresh, poor UX | |
| --- | |
| ### 6. Error Handling for Authentication Failures | |
| **Question**: How should authentication errors be communicated to users? | |
| **Decision**: Use standardized error responses with appropriate HTTP status codes | |
| **Research Findings**: | |
| - 401 Unauthorized: Authentication required or failed | |
| - 403 Forbidden: Authenticated but not authorized (not used in this spec) | |
| - Generic error messages prevent information leakage | |
| - Specific errors only in development mode | |
| **Implementation Pattern**: | |
| ```python | |
| # Backend error responses | |
| { | |
| "detail": "Invalid credentials", # Generic, doesn't reveal if email or password wrong | |
| "error_code": "AUTH_FAILED" | |
| } | |
| { | |
| "detail": "Token has expired", | |
| "error_code": "TOKEN_EXPIRED" | |
| } | |
| { | |
| "detail": "Invalid token", | |
| "error_code": "TOKEN_INVALID" | |
| } | |
| ``` | |
| **Security Considerations**: | |
| - Never reveal whether email exists in database | |
| - Never reveal which field (email/password) was incorrect | |
| - Log detailed errors server-side for debugging | |
| - Return generic errors to client | |
| --- | |
| ### 7. Database Schema Changes | |
| **Question**: What changes are needed to the existing User model? | |
| **Decision**: Add `password_hash` field to users table | |
| **Research Findings**: | |
| - Current User model has: id, email, name, created_at, updated_at | |
| - Need to add: password_hash (string, nullable=False) | |
| - Better Auth may also need its own tables for session management | |
| - Migration should be reversible | |
| **Implementation**: | |
| ```python | |
| # alembic/versions/002_add_user_password.py | |
| def upgrade(): | |
| op.add_column('users', sa.Column('password_hash', sa.String(255), nullable=False)) | |
| def downgrade(): | |
| op.drop_column('users', 'password_hash') | |
| ``` | |
| **Note**: Better Auth may create its own tables (sessions, accounts, etc.) - these should be in a separate migration or handled by Better Auth's migration system. | |
| --- | |
| ## Dependencies to Add | |
| ### Backend | |
| - `PyJWT==2.8.0` - JWT encoding/decoding | |
| - `passlib[bcrypt]==1.7.4` - Password hashing | |
| - `python-multipart==0.0.6` - Form data parsing (for login forms) | |
| ### Frontend | |
| - `better-auth` - Authentication library | |
| - `@better-auth/react` - React hooks for Better Auth | |
| --- | |
| ## Environment Variables | |
| ### Backend (.env) | |
| ``` | |
| BETTER_AUTH_SECRET=<shared-secret-min-32-chars> | |
| DATABASE_URL=<neon-postgres-url> | |
| ``` | |
| ### Frontend (.env.local) | |
| ``` | |
| BETTER_AUTH_SECRET=<same-shared-secret> | |
| NEXT_PUBLIC_API_URL=http://localhost:8000 | |
| ``` | |
| **Critical**: BETTER_AUTH_SECRET must be identical in both frontend and backend. | |
| --- | |
| ## Security Checklist | |
| - [x] Passwords hashed with bcrypt (cost factor 12) | |
| - [x] JWT tokens signed with HS256 and shared secret | |
| - [x] Tokens expire after 7 days | |
| - [x] httpOnly cookies prevent XSS attacks | |
| - [x] Generic error messages prevent information leakage | |
| - [x] HTTPS required in production (documented in assumptions) | |
| - [x] User ID extracted from validated token, not request parameters | |
| - [x] All task endpoints require authentication | |
| - [x] Database queries filtered by authenticated user ID | |
| --- | |
| ## Next Steps | |
| Phase 1 will use these research findings to: | |
| 1. Create data-model.md with User entity updates | |
| 2. Generate API contracts for auth endpoints | |
| 3. Create quickstart.md with setup instructions | |
| 4. Update agent context files with new dependencies | |