This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
IMPORTANT: This project requires Node.js >=22.0.0. Always use Node 22 for all commands:
# Use Node 22 (required for this project)
unset npm_config_prefix && source ~/.nvm/nvm.sh && nvm use 22# Install dependencies
pnpm install
# Setup HTTPS with Caddy
sudo caddy trust # Install Caddy's internal CA root to macOS trust store
sudo caddy run --config ./caddyfile # Run Caddy in another terminal window
# OR use the pnpm script:
pnpm caddyNote: All development commands require Node 22. Run unset npm_config_prefix && source ~/.nvm/nvm.sh && nvm use 22 first.
# Run all services in development mode with hot reload
# Note: This command includes NODE_EXTRA_CA_CERTS for Caddy's internal CA
pnpm dev
# Run individual services
pnpm dev:auth # Authorization Server
pnpm dev:api # Resource Server (API)
pnpm dev:app # Client App (Relying Party)
# Build all applications
pnpm build
# Lint code
pnpm lint
pnpm lint:fix # Auto-fix linting issues
# Production commands (after build)
pnpm --filter @apps/auth start # Start Auth Server in production
pnpm --filter @apps/api start # Start API in production
pnpm --filter @apps/app start # Start APP in productionThe current caddyfile configuration routes traffic as follows:
id.localtest.me→localhost:3001(OP)app.localtest.me→localhost:3004(Client App)api.localtest.me→localhost:3003(Resource Server)
If you prefer not to use sudo, you can modify the caddyfile to use high ports:
:8443 {
tls internal
reverse_proxy localhost:3001
}Then update the .env file to reflect the new URL pattern (e.g., https://localhost:8443).
This is an OpenID Connect (OIDC) implementation using Express for all services in a pnpm monorepo. The system consists of three main services:
- Located in
apps/auth - Built with
oidc-providerembedded in Express - Handles authentication, authorization, and token issuance
- Uses EJS templates for interaction views (login/consent)
- Includes Tailwind CSS for styling
- Currently uses in-memory storage (Postgres adapter planned)
- Default test user:
user@example.test/passw0rd! - Runs on port 3001
Key components:
- OIDC configuration (clients, claims, scopes)
- Basic interaction handlers (login and consent flows)
- In-memory user store (temporary)
- Tailwind CSS build process
Key features:
- Multiple client support: Configure multiple OIDC clients via
.env.clients.jsonorOIDC_CLIENTSenvironment variable - Refresh token support: Issues refresh tokens when
offline_accessscope is requested and client supportsrefresh_tokengrant type - Configurable TTLs: Session (1 day), Grant (1 year), AccessToken (1 hour), IdToken (1 hour), RefreshToken (14 days)
- Dynamic scope display: Consent screen shows requested scopes with human-readable descriptions
- Forced interactions: Authorization flow uses
prompt: "login consent"to always show login and consent screens - RP-initiated logout: Supports logout with automatic form submission
- Token signing (JWKS):
- Development: Uses ephemeral keys (auto-generated on startup with
kid="keystore-CHANGE-ME") - Production: Configure persistent JWKS via
JWKSenvironment variable to prevent token invalidation on restarts - Generate keys with:
node scripts/secrets.js jwks
- Development: Uses ephemeral keys (auto-generated on startup with
- Located in
apps/api - Protects resources with JWT validation using
jose - Validates tokens against the Auth server's JWKS endpoint
- Enforces scope-based authorization (e.g.,
accounts:read) - Includes customer and account data repositories
- Uses Pino for structured logging
- Runs on port 3003
- Implements FDX with Plaid's Core Exchange API specification (v6.3.1)
Key components:
- JWT verification middleware
- Protected resources requiring specific scopes
- Customer and account data models
- Repository pattern for data access
- Request validation utilities
- Public health endpoint
Available endpoints (FDX compliant):
/api/fdx/v6/customers/current- Get current customer information/api/fdx/v6/accounts- List customer accounts/api/fdx/v6/accounts/{accountId}- Get account details/api/fdx/v6/accounts/{accountId}/contact- Get account contact information/api/fdx/v6/accounts/{accountId}/statements- List account statements/api/fdx/v6/accounts/{accountId}/statements/{statementId}- Download statement PDF/api/fdx/v6/accounts/{accountId}/transactions- Get account transactions/api/fdx/v6/accounts/{accountId}/payment-networks- Get payment network information/api/fdx/v6/accounts/{accountId}/asset-transfer-networks- Get asset transfer network information
- Located in
apps/app - Acts as a Relying Party using
openid-client - Implements Authorization Code flow with PKCE
- Stores tokens in HTTP-only cookies (access token, refresh token, ID token)
- Makes authenticated calls to the API
- Uses EJS templates for views
- Includes Tailwind CSS for styling
- Runs on port 3004
Key components:
- OIDC client setup and discovery
- Login/callback handling with PKCE
- Token storage in secure cookies
- API calls with access token
- EJS view templates
- Tailwind CSS build process
Features:
- API Explorer: Interactive UI for testing all FDX Core Exchange endpoints with parameter inputs
- Token Inspector: View decoded ID token claims and user information at
/token - Token debug endpoint: View raw and decoded tokens at
/debug/tokens - Offline access: Requests
offline_accessscope to receive refresh tokens - Comprehensive scopes: Requests
openid,email,profile,offline_access, andaccounts:readscopes
- All services communicate via HTTPS using Caddy's internal CA (local) or Heroku's SSL (production)
- Local development: Caddy handles routing via
*.localtest.mesubdomains- Auth:
https://id.localtest.me(port 3001) - API:
https://api.localtest.me(port 3003) - APP:
https://app.localtest.me(port 3004)
- Auth:
- Production (Heroku): Deployed as Docker containers to Heroku apps with custom domains
- Auth:
https://auth.plaidypus.dev - API:
https://api.plaidypus.dev - App:
https://app.plaidypus.dev - See
docs/heroku-setup.mdfor full setup instructions
- Auth:
- Environment variables in
.envcontrol service configuration - TypeScript with ESM modules across all apps
- Shared TypeScript configuration via
tsconfig.base.json - ESLint configuration with TypeScript and Stylistic plugins
- Pino structured logging throughout
The authorization server supports multiple client configurations:
- Single client (default): Set
CLIENT_ID,CLIENT_SECRET, andREDIRECT_URIin.env - Multiple clients: Create
.env.clients.jsonfile in the project root (see.env.clients.example.json) - Environment variable: Set
OIDC_CLIENTSas a JSON string
Each client configuration must include:
client_id: Unique client identifierclient_secret: Client secret for authenticationredirect_uris: Array of allowed redirect URIspost_logout_redirect_uris: Array of allowed logout redirect URIsgrant_types: Array includingauthorization_codeand optionallyrefresh_tokenresponse_types: Array withcodetoken_endpoint_auth_method: Usuallyclient_secret_basic
Supported scopes:
openid- Basic identity (required)profile- Profile information (name)email- Email addressoffline_access- Offline access (enables refresh tokens)accounts:read- Read access to account data
Default token lifetimes (configured in apps/auth/src/index.ts):
- Session: 1 day (86400 seconds)
- Grant: 1 year (31536000 seconds)
- Access Token: 1 hour (3600 seconds)
- ID Token: 1 hour (3600 seconds)
- Refresh Token: 14 days (1209600 seconds)
The authorization server uses JWKS (JSON Web Key Set) to sign JWT tokens:
Development (default):
- No
JWKSenvironment variable needed oidc-providerauto-generates ephemeral keys on startup- Keys have
kid="keystore-CHANGE-ME"in JWT headers - Keys regenerate on each restart (invalidates all tokens)
- Perfectly acceptable for local development
Production (required):
- Set
JWKSenvironment variable with persistent signing keys - Prevents token invalidation on server restarts
- Enables proper key rotation strategy
- Generate with:
node scripts/secrets.js jwks - Store securely (AWS Secrets Manager, HashiCorp Vault, etc.)
- Contains private key material - never commit to version control
Why persistent keys matter:
- Tokens survive service restarts and deployments
- Multiple server instances can share the same keys
- Proper cryptographic key rotation
- Unique key IDs for debugging (e.g.,
key-abc123def456)
Configuration location: apps/auth/src/index.ts (lines 110-129) loads JWKS from environment and logs warnings if not set
This project implements security best practices for handling sensitive configuration and credentials.
All sensitive configuration is managed through environment variables:
| Variable | Purpose | Security Level |
|---|---|---|
CLIENT_SECRET |
OAuth client authentication | High - Never commit |
COOKIE_SECRET |
Session cookie signing | High - Never commit |
JWKS |
Token signing keys (contains private key) | Critical - Never commit |
OIDC_CLIENTS |
Multiple client configurations | High - Never commit |
POST_LOGOUT_REDIRECT_URI |
Post-logout redirect URL | Low - Configurable per environment |
The project provides .env.example templates at multiple levels:
- Root level:
.env.example- Complete configuration template - Auth server:
apps/auth/.env.example- Authorization server configuration - API server:
apps/api/.env.example- Resource server configuration - Client app:
apps/app/.env.example- Relying party configuration - Multiple clients:
apps/auth/.env.clients.example.json- Multi-client template
To set up a new environment:
# Copy the root template
cp .env.example .env
# Or copy individual app templates
cp apps/auth/.env.example apps/auth/.env
cp apps/api/.env.example apps/api/.env
cp apps/app/.env.example apps/app/.envUse the built-in secrets CLI to generate cryptographically secure values:
# Generate all secrets at once
node scripts/secrets.js all
# Or generate individual components
node scripts/secrets.js client # OAuth client credentials
node scripts/secrets.js secrets # Application secrets (COOKIE_SECRET)
node scripts/secrets.js jwks # Token signing keysThe .gitignore is configured to prevent accidental commits of sensitive files:
.env,.env.local,.env.prod,.env.production,.env.staging.env.clients.json(actual client configurations)*.pem,*.key,*.p12,*.pfx(certificate/key files)secrets.json,credentials.json,*-secrets.jsonservice-account*.json(cloud provider credentials)
Before deploying to production:
- Generate new secrets: Run
node scripts/secrets.js alland use the output - Never use dev values: Replace all
*-CHANGE-FOR-PRODUCTIONplaceholders - Use a secret manager: Store secrets in AWS Secrets Manager, HashiCorp Vault, or similar
- Rotate regularly: Establish a secret rotation schedule
- Audit access: Limit who can access production secrets
- Monitor for leaks: Use tools like git-secrets or truffleHog to scan for exposed credentials
The following credentials are intentionally hardcoded for development/demo purposes only:
- Test user:
user@example.test/passw0rd! - Blocked user:
blocked@example.test/passw0rd!
These are stored in-memory in apps/auth/src/index.ts and should be replaced with a proper user database and password hashing (bcrypt/argon2) for production use.
This project includes automated CI/CD pipelines and containerization support for secure, repeatable deployments.
| Workflow | Trigger | Purpose |
|---|---|---|
ci.yml |
PRs, push to main | Lint, build, security audit |
security.yml |
Weekly, dependency changes | CodeQL analysis |
deploy-heroku.yml |
Push to main, manual | Build and deploy all services to Heroku |
deploy-*.yml |
Push to paths | Deploy individual services to VM |
Runs on every pull request and push to main:
# Jobs run in parallel:
- Lint # ESLint validation
- Build # TypeScript compilation + CSS build
- Security # npm audit for vulnerabilitiesComprehensive security scanning:
- Dependency Audit: Weekly npm audit for known vulnerabilities
- CodeQL Analysis: Static analysis for security issues
Automated dependency updates via .github/dependabot.yml:
- npm packages: Weekly updates, grouped by type (production vs dev)
- GitHub Actions: Weekly updates for workflow actions
Each service has a production-ready Dockerfile with multi-stage builds:
# Build individual images
docker build -f apps/auth/Dockerfile -t core-exchange-auth .
docker build -f apps/api/Dockerfile -t core-exchange-api .
docker build -f apps/app/Dockerfile -t core-exchange-app .
# Or use docker-compose
docker compose up --buildDockerfile features:
- Multi-stage builds for minimal image size
- Non-root user for security
- Health checks for container orchestration
- Production-only dependencies
- Heroku PORT compatibility (maps dynamic
PORTto service-specific port vars)
Two compose configurations are provided:
| File | Purpose |
|---|---|
docker-compose.yml |
Local development with Docker |
docker-compose.prod.example.yml |
Production template (copy and customize) |
# Development
docker compose up --build
# Production (after customizing)
cp docker-compose.prod.example.yml docker-compose.prod.yml
# Edit docker-compose.prod.yml with production values
docker compose -f docker-compose.prod.yml up -d# Install dependencies (frozen lockfile for reproducibility)
pnpm install --frozen-lockfile
# Build all services
pnpm build
# Build individual services
pnpm --filter @apps/shared build
pnpm --filter @apps/auth build
pnpm --filter @apps/api build
pnpm --filter @apps/app build
# Run linting
pnpm lint
pnpm lint:fixBefore deploying to production:
- Use frozen lockfile: Always
pnpm install --frozen-lockfile - Pin Node.js version: Use exact version (22.x) in CI and Dockerfiles
- Run security audit:
pnpm audit --audit-level=high - Build verification: Ensure
pnpm buildsucceeds before deployment - Use secrets management: Never commit secrets; use environment variables or secret managers
- Enable branch protection: Require PR reviews and passing CI checks
Configure these settings on your main branch:
- Require pull request reviews before merging
- Require status checks to pass (CI workflow)
- Require branches to be up to date before merging
- Do not allow bypassing the above settings
- Visit
https://id.localtest.me/.well-known/openid-configurationto verify the Auth server is running - Go to
https://app.localtest.meand click "Login" - Use demo credentials:
user@example.test/passw0rd! - Approve consent (you'll see all requested scopes: openid, email, profile, offline_access, accounts:read)
- After redirect, explore the features:
- API Explorer (
/api-explorer): Test all FDX endpoints interactively - Token Inspector (
/token): View your ID token claims and user information - Token Debug (
/debug/tokens): Inspect raw and decoded access/ID/refresh tokens
- API Explorer (
The repository has identified the following future improvements:
- Implement a Postgres adapter for
oidc-providerfor persistent storage - Replace the in-memory user store with a DB table + proper password hashing
- Add E2E tests (Playwright/Cypress) to verify the authentication flow