juggernauth

module
v0.0.0-...-ab9459b Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 2, 2026 License: MIT

README

juggernauth

OAuth2 Authorization Server implementing the Authorization Code + PKCE flow with ES256 JWT tokens. Acts as a central auth authority for first-party services — users register and log in here, and downstream services validate JWTs locally via the JWKS endpoint.

How it works

OAuth2 authorization flow:

  1. User visits a service that needs authentication
  2. Service redirects to GET /oauth/authorize with a PKCE challenge
  3. User logs in (or is already logged in via session)
  4. juggernauth redirects back with an authorization code
  5. Service exchanges the code for a signed JWT at POST /oauth/token
  6. Service validates the JWT locally using the public key from GET /.well-known/jwks.json

Password reset flow:

  1. User submits their email at POST /auth/forgot-password
  2. juggernauth emails a reset link (requires RESEND_API_KEY)
  3. User clicks the link and submits a new password at POST /auth/reset-password
  4. All existing sessions are invalidated

Running locally

Prerequisites
  • Go 1.25+
  • templ CLI
  • PostgreSQL
Setup

1. Generate a signing key

openssl ecparam -name prime256v1 -genkey -noout | openssl ec -traditional

Copy the output and escape newlines for use in .env:

openssl ecparam -name prime256v1 -genkey -noout \
  | openssl ec -traditional \
  | awk '{printf "%s\\n", $0}' | sed 's/\\n$/\n/'

2. Create the database schema

psql -d your_database -f schema.sql

3. Create a .env file

APP_ENV=development
DB_CONNECTION_STRING=postgres://user:password@localhost:5432/juggernauth?sslmode=disable
BASE_URL=http://localhost:8080
JWT_PRIVATE_KEY_PEM=-----BEGIN EC PRIVATE KEY-----\nMHQCAQEE...\n-----END EC PRIVATE KEY-----
ALLOWED_REDIRECT_URIS=http://localhost:3000/auth/callback

# Optional: email sending via Resend (password reset emails)
RESEND_API_KEY=re_...
[email protected]

The PEM key must be on a single line with \n escaped newlines. Without RESEND_API_KEY, password reset emails are logged to stdout instead of sent.

4. Run

make run

This starts a live-reload server with templ watching for template changes. The app is available at http://localhost:8080.

Testing

# Unit tests only (no database required)
make test

# Integration tests (spins up a throwaway Postgres container)
make test-integration

# Coverage report — unit tests only
make coverage

# Coverage report — unit + integration tests (recommended)
make coverage-full

make coverage-full and make test-integration require Docker. The integration tests are skipped automatically when TEST_DB_CONNECTION_STRING is not set, so make test always works without a database.

Deployment

Build the binary
make build
# outputs dist/juggernauth
Docker
docker build -t juggernauth .
docker run -p 6553:6553 \
  -e DB_CONNECTION_STRING="postgres://..." \
  -e JWT_PRIVATE_KEY_PEM="$(cat ec-private.pem | awk '{printf "%s\\n", $0}')" \
  -e BASE_URL="https://auth.yourdomain.com" \
  -e ALLOWED_REDIRECT_URIS="https://service-a.yourdomain.com/callback,https://service-b.yourdomain.com/callback" \
  juggernauth
Environment variables
Variable Required Default Description
DB_CONNECTION_STRING yes PostgreSQL DSN
JWT_PRIVATE_KEY_PEM yes PEM-encoded ECDSA P-256 private key (single line, \n-escaped)
ALLOWED_REDIRECT_URIS no Comma-separated list of permitted OAuth redirect URIs
BASE_URL no http://localhost:8080 Public URL of this service (used in JWT iss claim)
SERVER_PORT no 8080 Port to listen on (6553 in Docker)
SESSION_TIMEOUT no 24h Browser session lifetime (e.g. 12h, 168h)
APP_ENV no development Set to production to enable secure cookies and other hardening
RESEND_API_KEY no Resend API key for sending password reset emails; if unset, emails are logged to stdout
EMAIL_FROM no [email protected] Sender address for outgoing emails
DB_MAX_CONNS no 10 Maximum number of database connections in the pool
DB_MIN_CONNS no 2 Minimum number of idle database connections in the pool
DB_CONNECT_TIMEOUT no 5s Timeout for establishing new database connections

Integrating a service

To protect a service with juggernauth:

  1. Add its callback URL to ALLOWED_REDIRECT_URIS
  2. Redirect unauthenticated users to:
    GET https://auth.yourdomain.com/oauth/authorize
      ?redirect_uri=https://service-a.yourdomain.com/callback
      &response_type=code
      &code_challenge=<base64url(SHA256(verifier))>
      &code_challenge_method=S256
      &state=<random>
    
  3. On callback, exchange the code:
    POST https://auth.yourdomain.com/oauth/token
    Content-Type: application/x-www-form-urlencoded
    
    grant_type=authorization_code
    &code=<code>
    &redirect_uri=https://service-a.yourdomain.com/callback
    &code_verifier=<verifier>
    
  4. Validate the returned JWT using the public key from GET /.well-known/jwks.json

JWT claims: iss (base URL), sub (user UUID), email, aud (redirect URI host), exp (15 min), jti.

Directories

Path Synopsis
cmd
juggernauth command
internal
app
ui
templ: version: v0.3.977
templ: version: v0.3.977
ui/auth
templ: version: v0.3.977
templ: version: v0.3.977
ui/pages
templ: version: v0.3.977
templ: version: v0.3.977

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL