2026-07-15 07:49:11 -04:00
2026-07-15 07:49:11 -04:00
2026-07-15 07:49:11 -04:00
2026-07-15 07:49:11 -04:00
2026-07-15 07:49:11 -04:00
2026-07-15 07:49:11 -04:00
2026-07-15 07:49:11 -04:00
2026-07-15 07:49:11 -04:00
2026-07-15 07:49:11 -04:00
2026-07-15 07:49:11 -04:00
2026-07-15 07:49:11 -04:00
2026-07-15 07:49:11 -04:00
2026-07-15 07:49:11 -04:00
2026-07-15 07:49:11 -04:00
2026-07-15 07:49:11 -04:00
2026-07-15 07:49:11 -04:00
2026-07-15 07:49:11 -04:00
2026-07-15 07:49:11 -04:00
2026-07-15 07:49:11 -04:00
2026-07-15 07:49:11 -04:00

dokku-google-auth

Put Google OAuth SSO in front of any dokku app with one command. Configure Google once, then:

dokku google-auth:enable my-app

Every request to my-app now requires a signed-in Google account from your organization. The app itself never touches OAuth — it just reads the signed-in user from request headers. Paths that should stay open (webhooks, API-key-protected endpoints, health checks) can be excluded per app.

How it works

                       ┌──────────────────────── dokku host ────────────────────────┐
                       │                                                            │
 browser ── https ──▶  │  nginx (per-app vhost, unchanged dokku routing)            │
                       │   │                                                        │
                       │   ├─ auth_request GET /_google-auth/verify ──┐             │
                       │   │       201? no: 302 to Google sign-in     │             │
                       │   │                                          ▼             │
                       │   │                          ┌────────────────────────┐    │
                       │   │                          │ google-auth-proxy      │    │
                       │   │                          │ (one shared container, │    │
                       │   │                          │  127.0.0.1:2999)       │    │
                       │   │                          └────────────────────────┘    │
                       │   ▼                                                        │
                       │  app container  ◀── X-Forwarded-Email / X-Forwarded-User  │
                       └────────────────────────────────────────────────────────────┘
  • One shared Go service (google-auth-proxy) runs in a single Docker container on the host, bound to 127.0.0.1. It holds the Google client credentials and handles the entire OAuth dance.
  • Per-app nginx config is injected through dokku's standard ~dokku/<app>/nginx.conf.d/ include. It uses nginx's auth_request module: every request triggers a fast subrequest to the auth service, which checks an encrypted session cookie. No valid session → the browser is redirected through Google sign-in and back.
  • Only one redirect URI is ever registered with Google (the "auth host"). When a user signs in to app-a.example.com, Google redirects to https://<auth-host>/_google-auth/callback; the service verifies the identity, then bounces the browser to app-a with a single-use, 60-second, encrypted hand-off token, which becomes a session cookie scoped to app-a's own domain. Adding app #47 therefore requires zero changes in the Google Console — apps don't even need to share a parent domain.
  • Sessions, OAuth state, and hand-off tokens are all AES-256-GCM encrypted and authenticated with a secret generated at configure time. Session cookies are Secure, HttpOnly, SameSite=Lax, and pinned to the host they were minted on.

Headers your apps receive

On every authenticated request, nginx injects (and strips anything the client tried to spoof):

Header Value
X-Forwarded-Email / X-Auth-Request-Email signed-in Google email (lowercased)
X-Forwarded-User / X-Auth-Request-User stable Google account id (sub claim)
X-Auth-Request-Name display name

On excluded paths these headers are set to the empty string, so your app can trust that a non-empty X-Forwarded-Email always came from the plugin.

How dokku plugins work (background)

A dokku plugin is just a git repo of executable shell scripts. Dokku clones it into /var/lib/dokku/plugins/available/<name> and:

  • plugin.toml — metadata (description, version).
  • install — runs as root at plugin:install / plugin:update time. This plugin uses it to create its data directory and docker build the Go auth service image (multi-stage build, so the host only needs Docker, not Go).
  • commands + subcommands/<name> — dokku dispatches dokku google-auth:enable foo to subcommands/enable, passing the full command line. commands provides dokku google-auth:help. This is why the plugin must be installed under the name google-auth.
  • Triggers — executables named after lifecycle hooks that dokku (via plugn) calls with arguments. This plugin implements:
    • nginx-pre-reload / core-post-deploy — regenerate the app's nginx.conf.d/google-auth.conf on every deploy, so it always references the app's current upstream (whose name changes if you remap ports).
    • post-delete, post-app-rename, post-app-clone — keep plugin state in sync with app lifecycle.
  • State lives under /var/lib/dokku/data/google-auth/ (global config + one directory per enabled app). Secrets are 0600.

The nginx integration relies on a stable, documented dokku feature: the generated vhost for every app contains include /home/dokku/<app>/nginx.conf.d/*.conf; inside the server block, which is exactly where this plugin drops its location blocks.

Installation

On the dokku host:

sudo dokku plugin:install https://github.com/<you>/dokku-google-auth.git --name google-auth

(The --name google-auth matters — subcommand dispatch is keyed off the plugin directory name.)

Requirements: dokku with the default nginx proxy (not traefik/caddy), Docker (always present on a dokku host), and nginx built with http_auth_request_module (true for stock Debian/Ubuntu nginx).

1. Create the Google OAuth client (one time, ever)

  1. Go to Google Cloud Console → APIs & Services → Credentials.

  2. Configure the OAuth consent screen if you haven't: Internal user type (recommended for a Workspace org — it automatically limits sign-in to your organization and skips app verification).

  3. Create Credentials → OAuth client ID → Web application.

  4. Add exactly one Authorized redirect URI:

    https://<auth-host>/_google-auth/callback
    

    <auth-host> is any HTTPS domain that routes to an app you will enable the plugin on. Pick your most permanent app's domain, or add a dedicated domain (e.g. auth.example.com) to one of your apps with dokku domains:add <app> auth.example.com (plus DNS + letsencrypt as usual). All other apps piggyback on it.

  5. Note the client ID and client secret.

2. Configure the plugin (one time)

dokku google-auth:configure \
  --client-id 1234567890-abc.apps.googleusercontent.com \
  --client-secret GOCSPX-xxxxxxxxxxxx \
  --auth-host auth.example.com \
  --allow-domain signal.org

This starts the shared auth service container and prints the callback URL to double-check against the Google Console.

Other flags (all optional, all persisted):

Flag Meaning Default
--allow-domain <d> allow any verified *@d account (repeatable; replaces the stored list)
--allow-email <e> allow a specific address, e.g. an outside collaborator (repeatable)
--session-ttl <dur> how long a sign-in lasts (24h, 72h, 30m, …) 24h
--cookie-name <n> session cookie name _google_auth
--port <p> host port (127.0.0.1 only) for the auth service 2999
--regenerate-cookie-secret rotate the session encryption key (signs everyone out)

3. Protect apps

dokku google-auth:enable my-app
dokku google-auth:enable other-app
...

That's it. Visit the app in a browser — you'll be bounced through Google and back.

4. Exclude paths (optional, per app)

# Path prefix — everything under it is open:
dokku google-auth:exclude my-app /api/webhooks

# Regex (prefix with re:):
dokku google-auth:exclude my-app 're:^/(healthz|metrics)$'

# List / remove:
dokku google-auth:exclude my-app
dokku google-auth:unexclude my-app /api/webhooks

Excluded paths proxy straight to your app with identity headers blanked — protect them yourself (API key, HMAC signature, etc.).

Day-to-day commands

dokku google-auth:report            # global + per-app status
dokku google-auth:report my-app     # one app
dokku google-auth:disable my-app    # turn SSO off for an app
dokku google-auth:logs -t           # follow auth service logs (sign-ins, denials)
dokku google-auth:restart           # restart the auth service

Users can check who they're signed in as at https://<any-app>/_google-auth/ and sign out at https://<any-app>/_google-auth/logout.

Things worth knowing

  • HTTPS is required in practice. Google refuses plain-HTTP redirect URIs and the session cookie is marked Secure. Use dokku-letsencrypt as usual. (--insecure-allow-http exists for local experiments only.)
  • The auth host must stay enabled. Google's callback lands on <auth-host>, so the app serving that domain must keep google-auth enabled. google-auth:disable warns you if you disable that app.
  • Sessions are per-domain. Signing in to app-a then visiting app-b triggers another round-trip through Google, but it's silent (already signed in) — the user just sees a quick redirect.
  • In-flight POSTs across an expired session get redirected to sign-in and are replayed as GETs — the POST body is lost. That's inherent to redirect-based SSO; keep session TTLs comfortable (e.g. 72h) if it bites.
  • Custom nginx location blocks in your own nginx.conf.d files: plain location /foo prefix blocks will be shadowed by this plugin's catch-all regex location — use location ^~ /foo in your own snippets if you need them to win.
  • Non-browser clients (curl, fetch without Accept: text/html) get a JSON 401 instead of a redirect chain.
  • Websockets work — upgrade headers are forwarded exactly like dokku's stock nginx template.
  • Plugin updates: sudo dokku plugin:update google-auth rebuilds the service image and restarts the container automatically.

Uninstall

dokku google-auth:disable <each-app>
dokku google-auth:stop
sudo dokku plugin:uninstall google-auth
sudo rm -rf /var/lib/dokku/data/google-auth
docker image rm dokku-google-auth:latest

Development

Tasks are defined in mise.toml (mise pins Go and shellcheck):

mise install          # install pinned toolchain
mise run test         # Go unit tests (full OAuth flow against a fake Google)
mise run test-nginx-conf  # generate nginx config + validate with real nginx in docker
mise run shellcheck   # lint all plugin scripts
mise run check        # everything CI would run
mise run docker-build # build the service image locally

Layout:

cmd/google-auth-proxy/   Go entrypoint
internal/authproxy/      OAuth flow, session crypto, HTTP handlers (+ tests)
functions                shared bash for the plugin (config store, nginx generation)
subcommands/             dokku google-auth:* commands
install, nginx-pre-reload, core-post-deploy, post-*   dokku lifecycle triggers
test/nginx-conf-test.sh  bash integration test for the generated nginx config
Dockerfile               multi-stage build → static binary in a scratch image
S
Description
No description provided
Readme
99 KiB
Languages
Shell 55.4%
Go 44.2%
Dockerfile 0.4%