Skip to content
Feeblo Docs
Esc
navigateopen⌘Jpreview
On this page

OAuth login

Register GitHub and Google OAuth apps, set client credentials, and test logins against a local OAuth emulator.

Your dashboard’s sign-in page shows “Continue with GitHub” and “Continue with Google” buttons. Each one works only after you register an OAuth app with the provider and put its credentials in the server’s environment. The providers themselves are optional: leave the variables empty and email and password login keeps working.

The auth server mounts better-auth under /api/auth on the API host, so every auth route, including the OAuth callbacks, lives on API_URL.

Callback URLs

Register one callback URL per provider. Both point at the API host:

Provider Callback URL
GitHub <API_URL>/api/auth/callback/github
Google <API_URL>/api/auth/callback/google

With API_URL=https://api.example.com, the GitHub callback is:

https://api.example.com/api/auth/callback/github

Providers compare the callback against their registered values exactly. Scheme, host, port, and path must match byte for byte, including the absence of a trailing slash. A redirect that ends at “redirect_uri mismatch” almost always means the registered URL and API_URL disagree on one of those pieces.

Client credentials

Set all four variables in the server environment:

Variable Notes
GITHUB_CLIENT_ID OAuth app client ID
GITHUB_CLIENT_SECRET OAuth app client secret
GOOGLE_CLIENT_ID OAuth client ID
GOOGLE_CLIENT_SECRET OAuth client secret

The server registers a provider only when both its ID and secret are set. Set one without the other and the button for that provider still renders, but clicks fail with an error from the auth server, so keep the pair together. AUTH_SIGN_UP_ENABLED=false keeps social sign-in working while switching off social sign-up: the provider config disables new-user registration, not logins.

Trusted origins and the sign-in flow

Sign-in starts from two places: the dashboard on APP_URL and public board subdomains of APP_ROOT_DOMAIN. Both must count as trusted origins, or better-auth rejects the request before the provider ever sees it.

Clicking a social button sends the browser to <API_URL>/api/auth/sign-in/social?provider=github&callbackURL=.... Better-auth checks the request’s Origin header and the callbackURL against trustedOrigins and aborts with INVALID_ORIGIN or INVALID_CALLBACK_URL when neither matches. From there it redirects to the provider’s authorize page, and the provider redirects back to the callback above. A successful exchange creates a 7-day session and redirects the browser back to the callbackURL.

The server builds its default trusted origin list from the same three variables:

  • APP_URL
  • API_URL
  • *.<APP_ROOT_DOMAIN> in production, *.localhost:3001 in development

The wildcard is what lets users sign in from any board subdomain. The docker-compose file derives the same default for you:

AUTH_TRUSTED_ORIGINS=$APP_URL,$API_URL,*.$APP_ROOT_DOMAIN

Set AUTH_TRUSTED_ORIGINS yourself and it replaces the defaults entirely, so include every origin your browsers call the API from. The list is comma-separated. Scheme-less patterns such as *.example.com match both http and https hosts; a pattern like https://*.example.com pins the scheme.

In development the web server proxies /api requests same-origin and sets host-only cookies per subdomain of *.localhost, which is why the dev trusted-origin default is *.localhost:3001 rather than a production wildcard.

Local OAuth emulator

For local development, GITHUB_EMULATOR_URL and GOOGLE_EMULATOR_URL point the providers at vercel-labs/emulate instead of the real services:

GITHUB_EMULATOR_URL=http://localhost:4000
GOOGLE_EMULATOR_URL=http://localhost:4001

The repo root’s emulate.config.yaml defines the emulator’s users and OAuth apps. The registered client credentials and redirect URIs live in that file:

  • GitHub app Iv1.example_client_id with redirect http://localhost:3000/api/auth/callback/github
  • Google client example-client-id.apps.googleusercontent.com with redirect http://localhost:3000/api/auth/callback/google

The emulator provider activates only when the emulator URL, the client ID, and the client secret are all set, and its credential values must match emulate.config.yaml. With the URL set, the server skips the built-in provider for that service. The portless URLs https://github.emulate.localhost and https://google.emulate.localhost work when your resolver maps .emulate.localhost.

Next steps

Was this page helpful?