Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

17 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

GitHub OAuth Server for Homelab

A simple Bun server that implements GitHub OAuth authentication for protecting multiple services in your homelab with a single authentication point.

Features

  • GitHub OAuth 2.0 authentication flow
  • Session management with cookies shared across subdomains
  • Auth verification endpoint for reverse proxy integration
  • CSRF protection with state parameter
  • Automatic session cleanup
  • Dynamic redirect back to requested app after authentication

Homelab Setup

This server is designed to protect multiple apps on subdomains with a single OAuth flow:

  • User visits app1.domain.com → redirected to auth → returns to app1.domain.com
  • User visits app2.domain.com → already authenticated (shared cookie) → access granted
  • Works for unlimited apps: app3.domain.com, app4.domain.com, etc.

1. Create a GitHub OAuth App

  1. Go to https://github.com/settings/developers
  2. Click "New OAuth App"
  3. Fill in the details:
    • Application name: Your homelab auth
    • Homepage URL: https://auth.mydomain.com
    • Authorization callback URL: https://auth.mydomain.com/auth/callback ⚠️ Must include /auth/callback
  4. Save the Client ID and Client Secret

2. Configure Environment

cp .env.example .env

Update .env for your homelab:

GITHUB_CLIENT_ID=your_github_client_id
GITHUB_CLIENT_SECRET=your_github_client_secret
PUBLIC_URL=https://auth.mydomain.com
PORT=3000
COOKIE_DOMAINS=.mydomain.com
ALLOWED_USERS=your_github_username
NODE_ENV=production

Important:

  • PUBLIC_URL - Your auth server's public URL (without /auth/callback). The callback URL is automatically constructed as PUBLIC_URL/auth/callback
  • COOKIE_DOMAINS - Comma-separated list of domains (with leading dot) for cookie sharing across subdomains
    • Single domain: COOKIE_DOMAINS=.domain.com
    • Multiple domains: COOKIE_DOMAINS=.domain1.com,.domain2.com
    • The server will automatically select the correct domain based on the request hostname
  • ALLOWED_USERS - Comma-separated list of GitHub usernames authorized to access. Leave empty to allow all users.

3. Choose Deployment Method

Option A: Run with Bun (Development)

bun install
bun run dev

Option B: Run with Bun (Production)

bun install --production
bun run src/index.ts

Option C: Run with Docker

docker run -d \
  --name github-oauth \
  -p 3000:3000 \
  -e GITHUB_CLIENT_ID=your_client_id \
  -e GITHUB_CLIENT_SECRET=your_client_secret \
  -e PUBLIC_URL=https://auth.mydomain.com \
  -e COOKIE_DOMAINS=.mydomain.com \
  -e ALLOWED_USERS=your_github_username \
  -e NODE_ENV=production \
  ghcr.io/YOUR_USERNAME/github-oauth:latest

Or with docker-compose:

services:
  github-oauth:
    image: ghcr.io/YOUR_USERNAME/github-oauth:latest
    ports:
      - "3000:3000"
    environment:
      - GITHUB_CLIENT_ID=your_client_id
      - GITHUB_CLIENT_SECRET=your_client_secret
      - PUBLIC_URL=https://auth.mydomain.com
      - COOKIE_DOMAINS=.mydomain.com
      - ALLOWED_USERS=your_github_username
      - NODE_ENV=production
    restart: unless-stopped

Or build locally:

docker build -t github-oauth .
docker run -d -p 3000:3000 --env-file .env github-oauth

API Endpoints

GET /auth/login?redirect=https://app1.domain.com

Initiates GitHub OAuth. Optional redirect parameter specifies where to return after auth.

GET /auth/callback

OAuth callback. Redirects user back to original app after successful authentication.

GET /auth/verify

Verifies authentication. Returns 200 if authenticated, 401 if not.

GET /auth/logout

Destroys session and clears cookies.

GET /health

Health check endpoint.

Caddy Configuration

Auth Server

auth.domain.com {
    reverse_proxy localhost:3000
}

This exposes your auth server at https://auth.domain.com. Caddy automatically handles HTTPS with Let's Encrypt.

Protected Apps

# this import takes two args
# args[0] is reverse proxy config : {{upstreams 8081}}
# args[1] is fqdn of the app : https://app1.domain.com
(github) {
	# github-oauth is the hosname of the container on the proxy network
    forward_auth github-oauth:3000 {
        uri /auth/verify
        copy_headers X-Auth-User

		@unauthorized status 401
		handle_response @unauthorized {
			# uri includes the full path requested by the user in the original request
			redir {env.AUTH_SERVER_URL}/auth/login?redirect={args[1]}{uri}
		}

		@error {
			status 403 500 502 503 504
		}
		handle_response @error {
			respond "An error occurred: {http.error.status_code}" {http.error.status_code}
		}
    }

    reverse_proxy {args[0]} # the actual app that is being protected
}

app1.domain.com {
   import github localhost:8081 https://app1.domain.com
}

app2.domain.com {
   import github localhost:8082 https://app2.domain.com
}

How it works:

  1. forward_auth localhost:3000 - Before proxying to your app, Caddy makes a subrequest to the auth server

    • uri /auth/verify - Calls the /auth/verify endpoint with the user's cookies
    • copy_headers X-Auth-User - Forwards auth headers to your app (optional)
  2. @error status 401 - Matches when /auth/verify returns 401 (not authenticated)

  3. handle_response @error - When user is not authenticated:

    • Redirects to https://auth.domain.com/auth/login
    • Adds ?redirect= parameter so user returns to the original URL after login
    • {uri} includes the full path (e.g., /some/page)
  4. reverse_proxy localhost:8081 - If authenticated, proxy to your actual app

Multiple Apps

Repeat for each app you want to protect:

All apps share the same session cookie because COOKIE_DOMAIN=.domain.com is set. Authenticate once, access all apps.

How It Works

  1. User visits https://app1.domain.com
  2. Reverse proxy calls /auth/verify to check authentication
  3. Not authenticated → redirect to https://auth.domain.com/auth/login?redirect=https://app1.domain.com
  4. OAuth flow completes
  5. Session cookie set with matching domain from COOKIE_DOMAINS (e.g., .domain.com)
  6. User redirected back to https://app1.domain.com
  7. Now all *.domain.com apps are accessible without re-authentication

OAuth Flow Details

Cookies Used:

  1. oauth_state (temporary, 10 min) - CSRF protection

    • Random UUID created on login
    • Sent to GitHub and verified on callback
    • Prevents attackers from forging login requests
  2. redirect_after_auth (temporary, 10 min) - Remembers destination

    • Stores the original URL user wanted to access
    • After OAuth completes, redirects user back to this URL
  3. github_auth_session (persistent, 24 hours) - Authentication session

    • Contains random session ID
    • Checked by Caddy on every request via /auth/verify
    • Session data (user info) stored server-side

The Complete Flow:

1. User visits app1.domain.com
   → No github_auth_session cookie
   → Caddy redirects to auth server

2. /auth/login
   → Sets oauth_state (CSRF protection)
   → Sets redirect_after_auth (remember destination)
   → Redirects to GitHub

3. GitHub OAuth
   → User authorizes
   → GitHub redirects back with code + state

4. /auth/callback
   → Verifies oauth_state matches (CSRF check)
   → Exchanges code for GitHub access token
   → Gets user info from GitHub API
   → Checks user whitelist (if configured)
   → Creates github_auth_session cookie
   → Clears temporary cookies
   → Redirects to original destination

5. Future requests
   → Caddy checks github_auth_session via /auth/verify
   → If valid → access granted
   → If expired/invalid → redirect to login

Cookie Configuration

The session cookie is configured to work across all subdomains:

  • Domain: Automatically selected from COOKIE_DOMAINS based on request hostname
  • Multiple Domains: Supports different domains in homelab (e.g., .domain1.com and .domain2.com)
  • HttpOnly: Prevents JavaScript access
  • Secure: HTTPS only (in production)
  • SameSite: Lax (allows navigation from GitHub)
  • Max-Age: 24 hours

Session Management

  • Sessions stored in-memory (for production: use Redis)
  • Sessions expire after 24 hours
  • Automatic cleanup every hour
  • Logout clears cookie across all subdomains

User Access Control

By default, any GitHub user can authenticate. To restrict access to specific users, set the ALLOWED_USERS environment variable:

ALLOWED_USERS=alice,bob,charlie
  • Comma-separated list of GitHub usernames (case-insensitive)
  • Users not in the list will see "Access denied" after GitHub OAuth
  • Leave empty or unset to allow all GitHub users

Production Tips

  1. Session Storage: Use Redis instead of in-memory for persistence
  2. HTTPS: Required for secure cookies (use Let's Encrypt with Caddy)
  3. User Whitelist: Use ALLOWED_USERS to restrict access to your homelab
  4. Rate Limiting: Protect auth endpoints
  5. Monitoring: Log authentication events

Docker Image

The Docker image uses oven/bun:1-alpine for a minimal footprint, resulting in:

  • Smaller final image size (~50MB)
  • Fast startup time
  • Includes Bun runtime and all dependencies

GitHub Actions automatically builds and publishes multi-platform Docker images (amd64 & arm64) to GitHub Container Registry on:

  • Every push to main branch (tagged as latest)
  • Every version tag (e.g., v1.0.0)

The images are available at: ghcr.io/YOUR_USERNAME/github-oauth

To pull and run:

docker pull ghcr.io/YOUR_USERNAME/github-oauth:latest

License

MIT

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages