Bun Backend Authentication
This document describes the authentication and authorization architecture in the Bun-based backend monolith.Overview
The authentication system is designed around Hexagonal Architecture principles, ensuring that the core business logic remains independent of the specific authentication provider. We use Better Auth as the primary authentication engine and JWT (JSON Web Tokens) for stateless session management across our API Gateway.Architecture & Design Patterns
1. Ports (Interfaces)
Defined in@bun-core/auth-ports, the AuthPort interface specifies the contract for authentication operations. This abstraction allows the system to remain agnostic of the underlying implementation.
- Primary Interface:
AuthPort - Models:
User,Session
2. Adapters (Implementation)
The@bun-core/auth-adapter package provides the concrete implementation of the AuthPort using Better Auth.
- Better Auth Integration: Manages PostgreSQL tables (
users,sessions,accounts,verifications,organizations,members,invitations,jwks) via Drizzle ORM. - Drizzle Adapter: Maps Better Auth models to our PostgreSQL schema using Drizzle’s type-safe query builder.
- JWT Service: A standalone service within the adapter that handles:
- Signing: Creates HS256 JWTs. During sign, enriches payload with user roles (from
user_rolestable) and organization context (fromuser_organizationstable) fetched from the database. - Verification: Validates tokens using shared HMAC secret (
BETTER_AUTH_SECRET).
- Signing: Creates HS256 JWTs. During sign, enriches payload with user roles (from
3. API Gateway Middleware
The Hono-based API gateway uses specialized middleware to enforce security at the entry points.- Auth Middleware:
- Extracts the
Authorization: Bearer <token>header. - Uses
JWTService.verify()to validate the token. - Attaches the decoded user payload to the Hono context (
c.user).
- Extracts the
- Authorization Middleware:
requireRole(...roles): Checks the user’s roles attached to the context against the required roles for the endpoint.
Authentication Flow
Sign-In / Sign-Up Flow
- Request: User submits credentials (Email/Password or Social Provider) to the monolith (
POST /api/auth/sign-inorPOST /api/auth/sign-up). - Better Auth Execution: Better Auth processes the request:
- For email/password: Validates email exists, verifies password hash
- For OAuth: Handles provider redirect, callback, and account linking
- Creates or updates record in the
authtable - Creates entry in
sessionstable for the new session - Creates
accountsrecord with provider details (OAuth) or hashed password (email/password)
- Token Issuance: Once Better Auth validates the user, the
JWTServiceis used to generate a stateless JWT.- Payload Enrichment: The JWT service queries the database to enrich the payload:
- Fetches user metadata from
authtable:email,emailVerified,isAnonymous,name - Fetches roles from
user_rolestable (all roles assigned to the user) - Fetches first organization from
user_organizationstable (if available) - Sets
isActive: trueas default (TODO: add isActive column to auth table if needed)
- Fetches user metadata from
- Token Format: HS256 signed JWT with payload containing all enriched user context
- Payload Enrichment: The JWT service queries the database to enrich the payload:
- Response: The JWT is returned to the client as response body and/or in a secure HTTP-only cookie (depending on flow).
Authenticated Request Flow
- Client Request: The client sends a request with the JWT in the
Authorization: Bearer <token>header. - Middleware Interception: The
authMiddlewarecatches the request and extracts the token. - Verification:
JWTService.verify(token)checks the signature, expiration, and integrity using the shared secret. - Payload Extraction: The JWT payload contains enriched user data:
- Context Injection: The middleware attaches user info to Hono context:
Note:
organizationIdand other fields are available in the full payload but not attached toc.userby default. Access viac.req.valid()or extend the middleware if needed. - Role Check: If the endpoint is protected by
requireRole('admin'), therequireRolemiddleware verifies if'admin'is present inc.user.roles. - Execution: If authorized, the request proceeds to the controller/handler.
Database Schema
The authentication system uses PostgreSQL tables managed by Drizzle ORM:| Table | Purpose | Notes |
|---|---|---|
| users | Better Auth core user table | ID (PK), email (unique), name, image, role, emailVerified, isAnonymous, timestamps |
| accounts | OAuth and email/password credentials | Stores provider tokens, refresh tokens, hashed passwords |
| sessions | Active user sessions | Tracks session token, expiration, IP address, user agent |
| verifications | Email verification codes | Stores verification tokens for email confirmation flow |
| organizations | Organization entities | Stores organization data (name, timestamps) |
| members | User-organization membership | Tracks which users belong to which organizations with per-org roles (owner, admin, member, staff, viewer) |
| invitations | Organization invitations | Pending organization invitations with role and status |
| jwks | JWT Key Set (Better Auth JWT plugin) | Stores public/private keys for JWT signing |
Components & File Mapping
| Component | Path | Package | Description |
|---|---|---|---|
| Auth Port | ports/auth.port.ts | @bun-core/auth-ports | Minimal interface definition for auth operations. Routes call authPort directly (no intermediate use cases). |
| Auth Adapter | better-auth.adapter.ts | @bun-core/auth-adapter | Thin bridge between Better Auth server API and route handlers. Delegates all operations to Better Auth. |
| JWT Service | jwt.service.ts | @bun-core/auth-adapter | HS256 signing and verification with DB enrichment (roles, org, user metadata). |
| Auth Middleware | api-gateway/middleware/auth.middleware.ts | Monolith | Hono middleware for Bearer token extraction and context mapping. |
| Auth Schema | schema.ts | @bun-core/auth-adapter | Database schema for Better Auth tables using Drizzle ORM. |
| Auth Context | auth-context.ts | @bun-core/auth-adapter | Attaches authenticated user data to request context. |
Custom use cases removed:
RegisterUserUseCase, LoginUserUseCase, UpdateUserProfileUseCase, GetUserUseCase, and UserRepository (MongoDB) have been deleted. Route handlers now call authPort directly, delegating to Better Auth APIs. See Better Auth User Module Consolidation for details.Technical Specifications
- Token Algorithm: HS256 with HMAC-SHA256
- Token Library:
jose(Node.js/Bun compatible) - Runtime: Bun
- Database ORM: Drizzle
- Database: PostgreSQL
- Token Verification: Stateless (via shared secret key in
BETTER_AUTH_SECRET) - Role Mapping: Persistent in
user_rolestable, enriched into JWT payload at signing time - Organization Mapping: Optional per-user organization in
user_organizationstable, included in JWT - Password Hashing: Better Auth default (scrypt) — custom password logic removed
- Session Duration: Configurable via JWT expiration time (
expiresInparameter)
Security Considerations
-
Secret Key Management:
BETTER_AUTH_SECRETmust be cryptographically random (32+ characters)- Keep secret server-side only, never expose to frontend
- Rotate if compromised
-
Token Validation:
- All tokens verified server-side via signature check
- Expiration time checked automatically by
joselibrary - No whitelist required (stateless verification)
-
Role Enrichment:
- Roles are queried from database at signing time, not stored in user input
- Frontend receives roles in JWT but cannot modify them
- Role changes reflected in next token issuance
-
Cross-Origin Requests:
- JWT sent as Bearer token in
Authorizationheader (suitable for CORS) - Alternative: HTTP-only secure cookie (suitable for same-origin requests)
- Configure based on frontend deployment location
- JWT sent as Bearer token in
Known Limitations & Future Improvements
-
Missing
isActiveColumn:- Currently hardcoded to
truein JWT enrichment - TODO: Add
is_activeboolean column toauthtable for user deactivation support
- Currently hardcoded to
-
Single Organization Context:
- JWT includes only first organization from
user_organizations - TODO: Support multiple organizations per user with context switching
- JWT includes only first organization from
-
Role Caching:
- Roles queried from DB at every token issuance (not cached)
- TODO: Consider caching with TTL if role assignment frequency is high
-
Email Verification:
- Email verification flow managed by Better Auth
- Currently not enforced during sign-up
- TODO: Implement optional email verification gate
-
Password Reset Flow:
- Not yet implemented
- TODO: Add forgot password + reset token flow