A production-ready authentication service built with Express and better-auth, providing secure user authentication with email/password and OAuth social providers.
- Framework: Express 5
- Authentication: better-auth
- Database: PostgreSQL (auth schema)
- Language: TypeScript
- Runtime: Node.js 24
- Email/password authentication
- GitHub OAuth integration
- JWT-based sessions
- Rate limiting
- CORS support
- Secure cookie handling
- PostgreSQL with separate auth schema
- Node.js 24+
- PostgreSQL 17+
- GitHub OAuth App (for social login)
- Install dependencies:
npm install- Copy and configure environment variables:
cp .env.example .env- Copy and configure Liquibase properties:
cp migrations/liquibase.properties.example migrations/liquibase.properties- Generate a secure secret key:
# Generate a cryptographically secure secret (min 32 characters)
openssl rand -base64 32-
Configure your
.envfile with:DATABASE_URL- PostgreSQL connection string (format:postgresql://user:password@host:port/database?schema=auth)BETTER_AUTH_SECRET- Use the generated secret key from step 4 (min 32 characters)GITHUB_CLIENT_IDandGITHUB_CLIENT_SECRET- GitHub OAuth credentials (optional)SENDGRID_API_KEY- Email service credentials (optional for development)FRONTEND_URL- Your frontend URL for CORS (default:http://localhost:5173)
-
Configure
migrations/liquibase.propertieswith:url- JDBC connection URL (format:jdbc:postgresql://host:port/database)username- Database usernamepassword- Database password
Note: This file is gitignored as it contains database credentials. Never commit it to version control.
The auth API uses a separate auth schema in PostgreSQL. Database migrations are managed by Liquibase:
- Navigate to the migrations folder:
cd ../migrations- Run Liquibase migrations:
liquibase update -Dcontexts=developmentNote: Better-auth will also automatically create/update its required tables on first run.
# Run in development mode with hot reload
npm run dev
# Build TypeScript
npm run build
# Run production build
npm startProduction: https://auth.domain.com
Staging: https://auth-staging.domain.com
Local Development: http://localhost:3002
All auth routes are at the root path (no /api/auth/ prefix):
POST /sign-up- Register new userPOST /sign-in- Sign in with email/passwordPOST /sign-out- Sign out userGET /session- Get current sessionGET /github- GitHub OAuth flowGET /callback/github- GitHub OAuth callbackGET /jwks- JWKS endpoint for JWT validation
GET /health- Health check endpointGET /- API info
- Helmet.js for security headers
- Rate limiting (100 requests per 15 minutes)
- CORS configuration for trusted origins
- Secure cookie settings in production
- Password hashing with bcrypt
- JWT token signing
docker build -f Dockerfile.dev -t auth-api:dev .
docker run -p 3002:3002 --env-file .env auth-api:devThe production Dockerfile uses multi-stage builds for optimal image size and security:
# Build production image
docker build -t auth-api:latest .
# Run production container
docker run -p 3002:3002 --env-file .env auth-api:latestProduction Features:
- Multi-stage build for minimal image size
- Non-root user for enhanced security
- Health checks for container orchestration
- Signal handling with dumb-init
- Production-only dependencies
| Variable | Description | Default |
|---|---|---|
PORT |
Server port | 3002 |
NODE_ENV |
Environment | development |
DATABASE_URL |
PostgreSQL connection string | - |
BETTER_AUTH_SECRET |
Secret for JWT signing (min 32 chars) | - |
BETTER_AUTH_URL |
Base URL for auth (subdomain: auth.domain.com) | http://localhost:3002 |
GITHUB_CLIENT_ID |
GitHub OAuth client ID | - |
GITHUB_CLIENT_SECRET |
GitHub OAuth client secret | - |
FRONTEND_URL |
Frontend URL for CORS (domain.com) | http://localhost:5173 |
API_URL |
Core API URL for CORS (api.domain.com) | http://localhost:3001 |
SESSION_EXPIRES_IN |
Session duration in seconds | 86400 |
SESSION_UPDATE_AGE |
Session refresh interval | 3600 |
# Run tests
npm test
# Test coverage
npm run test:coverageThis repository is designed to work as a standalone service or as a Git submodule in a larger monorepo.
When first cloning a parent repository that includes this as a submodule:
# In the parent repository
git submodule update --init --recursive
cd auth-api
npm install
cp .env.example .env
cp migrations/liquibase.properties.example migrations/liquibase.properties
# Configure both files with your credentials# Update submodule to latest commit
git submodule update --remote auth-api
# Commit submodule changes in parent repo
git add auth-api
git commit -m "Update auth-api submodule"The following files are gitignored and must be configured locally:
.env- Application environment variablesmigrations/liquibase.properties- Database migration credentials
These files contain project-specific credentials and should never be committed to version control.
MIT License - See LICENSE file for details