Skip to content

feat: connect planner via OAuth 2.1 / OIDC#32

Open
mroderick wants to merge 4 commits into
mainfrom
feature/magic-link-code-exchange
Open

feat: connect planner via OAuth 2.1 / OIDC#32
mroderick wants to merge 4 commits into
mainfrom
feature/magic-link-code-exchange

Conversation

@mroderick

@mroderick mroderick commented Jun 18, 2026

Copy link
Copy Markdown
Collaborator

Summary

Connect the auth service to the planner via OAuth 2.1 / OIDC using Better Auth's built-in oauthProvider and jwt plugins. The planner acts as a first-party OAuth client.

Changes

Auth configuration (src/auth.js)

  • Add jwt() plugin — issues id_tokens with email and name claims for the planner
  • Add oauthProvider() plugin — exposes OAuth 2.1 authorize/token endpoints (/api/auth/oauth2/*)
  • Configure trustedOrigins for localhost / 127.0.0.1 in dev

Config (src/config.js)

  • Add AUTH_DEFAULT_PORT and PLANNER_DEFAULT_PORT constants
  • Update base_url, planner_url, and allowed_redirects to use the planner callback

Login flow (src/app/routes/auth.js, src/app/components/login.js)

  • Replace redirect_url-based flow with callbackURL pointing to the OAuth authorize endpoint
  • Extract named route handlers (showLogin, showMagicLinkForm, sendMagicLink, startGitHubOAuth)
  • getCallbackURL preserves OAuth query params (PKCE, state, etc.) from the authorize request
  • GitHub and magic link buttons now POST/GET with callbackURL

Database seeding

  • src/app/db/seed-client.js — inserts the planner OAuth client via raw SQL (Better Auth has no public API for this without admin auth)
  • scripts/migrate.js — runs migrations and seeds the planner client (for Heroku release phase)

Dev test helpers

  • src/dev/magic-links.js — captures sent magic links when SENDGRID_API_KEY is unset
  • src/app/app.js — exposes /api/test/magic-links GET/DELETE in non-production environments

CI

  • Add e2e job running Playwright after unit tests
  • Use chromium-headless-shell for smaller download
  • Add permissions: contents: read at workflow level for zizmor compliance

Testing

158 unit tests, 0 failures. New tests cover:

  • OAuth provider: planner client seeded correctly (public, PKCE-required)
  • Authorize endpoint: redirects unauthenticated users to login, issues code when authenticated
  • Token endpoint: exchanges code for access token with PKCE, rejects missing/invalid PKCE, invalid code, mismatched redirect_uri
  • Integration: full flow — authenticate → authorize → exchange code → verify JWT via JWKS
  • E2E (Playwright): unauthenticated authorize → login → magic link → verify → authorize → code

@mroderick mroderick force-pushed the feature/magic-link-code-exchange branch 3 times, most recently from 2c4d394 to 9826035 Compare June 18, 2026 09:42
Comment thread src/app/routes/auth.js Outdated
Comment thread src/auth.js
@mroderick mroderick force-pushed the feature/magic-link-code-exchange branch 6 times, most recently from f302a42 to 5ff85f2 Compare June 21, 2026 21:16
Comment thread .github/workflows/ci.yml Fixed
Comment on lines +26 to +39
smoke:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version-file: ".nvmrc"
- run: npm ci
- run: |
npm run prettier:check
npm run lint
npm run fallow
Comment thread .github/workflows/ci.yml Fixed
Install @better-auth/oauth-provider and configure Better Auth with:
- jwt plugin: issues id_tokens with email/name claims for the planner
- oauthProvider plugin: OAuth 2.1 authorize/token endpoints for the planner

Add seedPlannerClient helper that inserts the first-party planner client
via raw SQL (Better Auth has no public API for this without admin auth).
Add a migration script for Heroku release phase that runs migrations
and seeds the planner client.

Add OAuth provider feature tests covering:
- planner client seeded correctly (public, PKCE-required)
- authorize redirects unauthenticated users to login
- authorize issues code when authenticated
- token endpoint exchanges code for access token with PKCE
- token endpoint rejects missing/invalid PKCE, invalid code, mismatched redirect_uri
Replace the old redirect_url-based login flow with callbackURL that
points to the OAuth provider's authorize endpoint.

- Extract named route handlers (showLogin, showMagicLinkForm, sendMagicLink,
  startGitHubOAuth) for clarity
- getCallbackURL preserves OAuth query params from the authorize request
  and falls back to a default authorize URL for direct login
- GitHubButton and MagicLinkButton now POST/GET with callbackURL instead
  of using client-side JS or redirect_url

Add integration test that exercises the full flow:
  authenticate -> authorize -> exchange code -> verify JWT
@mroderick mroderick force-pushed the feature/magic-link-code-exchange branch from 5ff85f2 to 714f6dc Compare June 21, 2026 21:26
Add a headless browser test that exercises the full OAuth 2.1 flow:
  unauthenticated authorize -> login -> magic link -> verify -> authorize -> code

- Capture magic links in dev via devMagicLinks store when SENDGRID_API_KEY
  is not set
- Expose /api/test/magic-links GET/DELETE endpoints for tests to read/clear
  captured links (dev-only, not available in production)
- Use APIRequestContext for magic link verification so cookies are handled
  automatically across requests
- Add e2e job to CI that runs after unit tests, using chromium-headless-shell
  for a smaller Playwright download
@mroderick mroderick force-pushed the feature/magic-link-code-exchange branch from 714f6dc to 215a38d Compare June 21, 2026 21:32
@mroderick mroderick marked this pull request as ready for review June 21, 2026 21:42
@mroderick mroderick requested a review from till June 21, 2026 21:42
@mroderick mroderick changed the title feat: add code exchange endpoint and auth callback for planner integration feat: connect planner via OAuth 2.1 / OIDC Jun 21, 2026
@mroderick mroderick force-pushed the feature/magic-link-code-exchange branch from 1092615 to 82dbb02 Compare June 22, 2026 10:28
The jwt ruby gem (3.2.0) does not support EdDSA signatures.
Configure Better Auth to use RS256 instead so the planner can
verify id_tokens without additional dependencies.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants