← Back to docs

Authentication

How getbot authenticates users with Google SSO — no passwords, no user database.

demo
Demo: Authentication

Google SSO, locally verified

getbot uses Google Sign-In for authentication but doesn't trust any central service to make access decisions. Here's how it works: a central auth server (auth.getbot.run) handles Google OAuth, but the actual access control — who can reach your bot — is enforced locally on your server by the getbot-auth binary.

The auth flow

When you visit your bot's URL for the first time:

  1. Caddy intercepts the request — checks for a valid session cookie via forward_auth
  2. No cookie found — Caddy asks getbot-auth to verify. Since there's no session, it redirects to the login page
  3. Google Sign-In — you authenticate with Google on auth.getbot.run
  4. Auth code returnedauth.getbot.run redirects back to your server with an authorization code
  5. Code exchangegetbot-auth on your server exchanges the code with auth.getbot.run to get your email
  6. Allowlist check — your email is checked against the org's allowed emails list
  7. JWT minted locallygetbot-auth creates a signed JWT cookie on your server using a key that never leaves your machine
  8. Access granted — subsequent requests include the cookie, Caddy validates it, and you reach your bot

What stays on your server

The signing key for JWT tokens is generated locally and stored at /etc/getbot/auth.json. It never leaves your server. The central auth.getbot.run service only sees your email during the OAuth flow — it doesn't have the signing key, can't mint tokens for your bots, and can't read your bot's traffic.

Who can access your bot

Access is controlled by an email allowlist in /etc/getbot/auth.json. When you deploy a bot with alice@acme.com, that email is added to the acme org's allowlist. When you deploy a second bot with bob@acme.com, Bob is added too. Both can access all bots in the acme org.

Only emails on the allowlist can sign in. Google authenticates the user's identity, but your server decides whether to grant access.

The session cookie

After successful authentication, getbot-auth sets a cookie called _getbot_session:

  • Expiry — 24 hours. After that, you'll need to sign in again.
  • HttpOnly — JavaScript can't read it (prevents XSS token theft)
  • Secure — only sent over HTTPS
  • SameSite: Lax — prevents CSRF while allowing normal navigation

The JWT inside the cookie contains your email, org, and expiry time. It's signed with HS256 using the org's 256-bit signing key.

Key rotation

Each org's auth config supports two signing keys: a current key and a previous key. When you rotate the signing key, existing sessions (signed with the old key) continue to work for up to 24 hours. After that, users re-authenticate and get tokens signed with the new key.

Security protections

  • CSRF protection — a random state parameter is stored in a short-lived cookie and validated on callback
  • Open redirect prevention — the redirect URL after login must be a relative path (can't redirect to external sites)
  • Rate limiting — code exchange endpoint is limited to 10 requests per minute per IP
  • Localhost onlygetbot-auth listens on 127.0.0.1:9099, not accessible from the internet. All external traffic goes through Caddy.
  • Algorithm pinning — JWT validation only accepts HS256, preventing algorithm confusion attacks

How it fits together

  Browser → Caddy (HTTPS, port 443)
              ↓ forward_auth
           getbot-auth (localhost:9099)
              ↓ verify JWT cookie
           [valid] → proxy to bot container
           [invalid] → redirect to Google Sign-In
              ↓ OAuth callback
           auth.getbot.run → code → getbot-auth
              ↓ exchange + allowlist check
           mint JWT → set cookie → redirect to bot

The result: your bot is accessible via HTTPS with Google SSO, but the authentication keys and access decisions stay on your server. No vendor has the ability to access your bot or read your conversations.