Add Ory Auth.js integration#342
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
PR SummaryHigh Risk Overview When Ory is on, the previous stub provider is replaced with real session resolution; sign-in / sign-up / forgot-password auto-redirect to hosted Ory instead of local forms. On first OAuth sign-in, JWT claims drive an admin API clients now use Reviewed by Cursor Bugbot for commit b07a33a. Bugbot is set up for automated code reviews on this repo. Configure here. |
There was a problem hiding this comment.
Pull request overview
This PR introduces an Auth.js + Ory Hydra authentication mode (behind AUTH_PROVIDER) alongside the existing Supabase auth path, and updates request authentication/header wiring plus supporting dashboard-api contract types and tests.
Changes:
- Add Auth.js (NextAuth) Ory Hydra OAuth integration with session handling, sign-in bootstrap, and Ory sign-out flows.
- Unify API auth header generation via
authHeaders()and route server-side auth through a provider abstraction (auth/authAdmin). - Update OpenAPI spec + generated dashboard-api types and add unit/integration tests covering Ory auth paths.
Reviewed changes
Copilot reviewed 85 out of 86 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| vitest.config.ts | Inline Auth.js deps for Vitest resolution. |
| tests/unit/teams-repository.test.ts | Update mocks for new authAdmin dependency. |
| tests/unit/keys-repository.test.ts | Update expected error string for provider-agnostic auth. |
| tests/unit/auth-supabase-provider.test.ts | Add unit tests for SupabaseAuthProvider. |
| tests/unit/auth-supabase-admin.test.ts | Add unit tests for supabaseAuthAdmin. |
| tests/unit/auth-ory-provider.test.ts | Add unit tests for oryAuthProvider behavior. |
| tests/unit/auth-headers.test.ts | Add unit tests for header switching. |
| tests/setup.ts | Provide placeholder env for module-load clients. |
| tests/integration/proxy.test.ts | Extend proxy integration mocks for getSession. |
| tests/integration/auth-ory-bootstrap.test.ts | Add integration coverage for Ory bootstrap call. |
| src/proxy.ts | Add Ory-aware middleware auth integration. |
| src/lib/utils/server.ts | Switch token header generation to authHeaders(). |
| src/lib/utils/auth.ts | Remove Supabase-only provider extraction helper. |
| src/lib/env.ts | Add Ory/Auth.js env variables to schema. |
| src/features/dashboard/terminal/sandbox-session.ts | Use authHeaders() for E2B SDK calls. |
| src/features/dashboard/sandbox/inspect/context.tsx | Use authHeaders() for inspect sandbox headers. |
| src/features/dashboard/context.tsx | Use AuthUser instead of Supabase User. |
| src/features/dashboard/account/password-settings.tsx | Use AuthUser.providers for email provider checks. |
| src/features/dashboard/account/name-settings.tsx | Use AuthUser.name in forms and comparisons. |
| src/features/dashboard/account/email-settings.tsx | Use AuthUser.providers for email provider checks. |
| src/features/auth/ory-hosted-auth-redirect.tsx | Add client redirect component for hosted Ory auth. |
| src/core/shared/contracts/dashboard-api.types.ts | Regenerate dashboard-api types with new admin endpoints. |
| src/core/server/trpc/init.ts | Update TRPC context session/user types for AuthUser. |
| src/core/server/functions/sandboxes/get-team-metrics-max.ts | Switch to authHeaders() for infra calls. |
| src/core/server/functions/sandboxes/get-team-metrics-core.ts | Switch to authHeaders() for infra calls. |
| src/core/server/functions/auth/get-user-by-token.ts | Remove Supabase-specific cached token lookup. |
| src/core/server/functions/auth/get-session.ts | Remove Supabase getSessionInsecure helper. |
| src/core/server/auth/types.ts | Introduce provider-agnostic auth types. |
| src/core/server/auth/supabase/user.ts | Map Supabase User -> AuthUser. |
| src/core/server/auth/supabase/server-client.ts | Add SSR client wrappers for proxy/headers contexts. |
| src/core/server/auth/supabase/provider.ts | Add SupabaseAuthProvider implementing AuthProvider. |
| src/core/server/auth/supabase/flows.ts | Centralize Supabase auth flows behind helpers. |
| src/core/server/auth/supabase/admin.ts | Add supabaseAuthAdmin implementing AuthAdmin. |
| src/core/server/auth/provider.ts | Define AuthProvider interface. |
| src/core/server/auth/ory/signout.ts | Add signed state + redirect helpers for Ory logout. |
| src/core/server/auth/ory/provider.ts | Add oryAuthProvider using Auth.js session. |
| src/core/server/auth/ory/identity.ts | Map Auth.js session/Ory identity -> AuthUser. |
| src/core/server/auth/ory/client.ts | Add cached Ory Identity API client. |
| src/core/server/auth/ory/bootstrap.ts | Add dashboard-api bootstrap on Auth.js sign-in event. |
| src/core/server/auth/ory/admin.ts | Add oryAuthAdmin for identity/email lookups. |
| src/core/server/auth/index.ts | Wire auth/authAdmin selection + helpers. |
| src/core/server/auth/admin.ts | Define AuthAdmin interface. |
| src/core/server/api/middlewares/telemetry.ts | Update telemetry middleware to AuthUser. |
| src/core/server/api/middlewares/auth.ts | Switch TRPC auth middleware to provider-based auth. |
| src/core/server/actions/user-actions.ts | Use supabaseAuthFlows + provider signOut. |
| src/core/server/actions/sandbox-actions.ts | Switch to authHeaders() for infra DELETE. |
| src/core/server/actions/ory-auth-actions.ts | Add server action wrapper for Auth.js signIn. |
| src/core/server/actions/client.ts | Switch safe-action auth context to provider-based. |
| src/core/server/actions/auth-actions.ts | Route Supabase flows through supabaseAuthFlows; add Ory sign-out redirect. |
| src/core/modules/webhooks/repository.server.ts | Replace SUPABASE_AUTH_HEADERS with authHeaders. |
| src/core/modules/users/auth-user-emails.server.ts | Resolve creator emails via authAdmin (provider-agnostic). |
| src/core/modules/templates/repository.server.ts | Replace SUPABASE_AUTH_HEADERS with authHeaders. |
| src/core/modules/teams/user-teams-repository.server.ts | Replace SUPABASE_AUTH_HEADERS with authHeaders. |
| src/core/modules/teams/teams-repository.server.ts | Replace Supabase admin lookups with authAdmin.getUserById. |
| src/core/modules/sandboxes/repository.server.ts | Replace SUPABASE_AUTH_HEADERS with authHeaders. |
| src/core/modules/keys/repository.server.ts | Replace SUPABASE_AUTH_HEADERS with authHeaders. |
| src/core/modules/builds/repository.server.ts | Replace SUPABASE_AUTH_HEADERS with authHeaders. |
| src/core/modules/billing/repository.server.ts | Replace SUPABASE_AUTH_HEADERS with authHeaders. |
| src/core/modules/auth/repository.server.ts | Route verifyOtp via supabaseAuthFlows helper. |
| src/configs/flags.ts | Add isOryAuthEnabled() feature switch. |
| src/configs/api.ts | Introduce authHeaders() + Ory team header constant. |
| src/auth.ts | Add Auth.js (NextAuth) Ory Hydra provider configuration + token refresh. |
| src/app/sbx/new/route.ts | Switch route auth to provider-based auth + authHeaders. |
| src/app/dashboard/terminal/page.tsx | Switch server auth to provider-based auth + authHeaders. |
| src/app/dashboard/route.ts | Switch server auth to provider-based auth + Ory sign-out on missing team. |
| src/app/dashboard/account/route.ts | Switch server auth to provider-based auth + Ory sign-out on missing team. |
| src/app/dashboard/[teamSlug]/team-gate.tsx | Update prop type to AuthUser. |
| src/app/dashboard/[teamSlug]/layout.tsx | Switch server auth to provider-based auth. |
| src/app/dashboard/(resolvers)/inspect/sandbox/[sandboxId]/route.ts | Switch server auth to provider-based auth + authHeaders. |
| src/app/api/teams/[teamSlug]/metrics/route.ts | Switch route auth to provider-based auth. |
| src/app/api/auth/oauth/signout-flow/route.ts | Add Ory logout flow endpoint. |
| src/app/api/auth/oauth/signed-out/route.ts | Add Ory post-logout callback endpoint. |
| src/app/api/auth/oauth/[...nextauth]/route.ts | Add NextAuth handlers route. |
| src/app/api/auth/email-callback/route.tsx | Route exchangeCodeForSession via supabaseAuthFlows and log errors. |
| src/app/api/auth/callback/route.ts | Route exchangeCodeForSession via supabaseAuthFlows. |
| src/app/(auth)/sign-up/signup-form.tsx | Extract existing signup form into client component. |
| src/app/(auth)/sign-up/page.tsx | Redirect to hosted Ory auth when enabled. |
| src/app/(auth)/sign-in/page.tsx | Redirect to hosted Ory auth when enabled. |
| src/app/(auth)/sign-in/login-form.tsx | Extract existing login form into client component. |
| src/app/(auth)/forgot-password/page.tsx | Redirect to hosted Ory auth when enabled. |
| src/app/(auth)/forgot-password/forgot-password-form.tsx | Extract existing forgot-password form into client component. |
| src/app/(auth)/auth/cli/page.tsx | Switch server auth to provider-based auth for CLI flow. |
| spec/openapi.dashboard-api.yaml | Add auth provider security schemes + new admin endpoints. |
| package.json | Add next-auth and @ory/client-fetch dependencies. |
| bun.lock | Lockfile updates for new dependencies. |
| .env.example | Document Ory/Auth.js env variables and usage. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| export const authHeaders = ( | ||
| token: string, | ||
| teamId?: string | ||
| ): Record<string, string> => { | ||
| const isOry = isOryAuthEnabled() | ||
| const headers: Record<string, string> = isOry | ||
| ? { Authorization: `Bearer ${token}` } | ||
| : { [SUPABASE_TOKEN_HEADER]: token } | ||
| if (teamId) { | ||
| headers[isOry ? AUTH_PROVIDER_TEAM_HEADER : SUPABASE_TEAM_HEADER] = teamId | ||
| } | ||
| return headers |
| const proxyWithOryAuth = authjsMiddleware(async (req, _event: NextFetchEvent) => | ||
| proxyCore(req, !!req.auth) | ||
| ) |
| export async function signInWithOryAction(formData: FormData) { | ||
| const returnTo = formData.get('returnTo') | ||
| const redirectTo = | ||
| typeof returnTo === 'string' && returnTo.length > 0 | ||
| ? returnTo | ||
| : '/dashboard' | ||
| await signIn('ory', { redirectTo }) |
| AUTH_PROVIDER: z.enum(['supabase', 'ory']).optional(), | ||
| AUTH_SECRET: z.string().min(1).optional(), | ||
| AUTH_TRUST_HOST: z.string().optional(), | ||
| ORY_SDK_URL: z.url().optional(), | ||
| ORY_OAUTH2_CLIENT_ID: z.string().min(1).optional(), | ||
| ORY_OAUTH2_CLIENT_SECRET: z.string().min(1).optional(), | ||
| ORY_OAUTH2_AUDIENCE: z.string().min(1).optional(), | ||
| ORY_PROJECT_API_TOKEN: z.string().min(1).optional(), | ||
|
|
| try { | ||
| const credentials = btoa(`${clientId}:${clientSecret}`) | ||
| const tokenEndpoint = `${sdkUrl.replace(/\/$/, '')}/oauth2/token` | ||
| const response = await fetch(tokenEndpoint, { | ||
| method: 'POST', | ||
| headers: { | ||
| Authorization: `Basic ${credentials}`, | ||
| 'Content-Type': 'application/x-www-form-urlencoded', | ||
| }, |
| }; | ||
| }; | ||
| }; | ||
| put?: never; |
Wire the dashboard to Ory through Auth.js while preserving Supabase mode behind the auth provider switch.
0b18e9c to
77a5a85
Compare
|
|
||
| const userId = data.session.user.id | ||
| const headers = SUPABASE_AUTH_HEADERS(data.session.access_token, teamId) | ||
| const headers = authHeaders(data.session.access_token, teamId) |
There was a problem hiding this comment.
Ory breaks browser terminal inspect
High Severity
With AUTH_PROVIDER=ory, browser terminal and sandbox inspect still call supabase.auth.getSession() for E2B SDK headers. There is no Supabase session in Ory mode, so connect/create fails or sends users to sign-in despite a valid Auth.js session.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 0386667. Configure here.
| } | ||
|
|
||
| const proxyWithOryAuth = authjsMiddleware(async (req, _event: NextFetchEvent) => | ||
| proxyCore(req, !!req.auth) |
There was a problem hiding this comment.
🔒 Agentic Security Review
Severity: MEDIUM
The Ory middleware path marks a request authenticated using !!req.auth instead of the stricter provider validation used elsewhere (getAuthContext() rejects sessions with missing user.id, missing accessToken, or session.error). This creates an auth-context mismatch in proxy routing decisions.
Impact: Requests tied to invalid/errored sessions can be treated as authenticated for middleware redirect logic, weakening the intended authentication gate on protected navigation paths.
Move the sign-out redirect target into the AuthProvider contract so route handlers drop their isOryAuthEnabled() branches. Delete the HMAC-state sign-out machinery (we no longer thread a "you were signed out because X" message through Hydra). Extract refreshOryToken into its own module and convert the bootstrap import to a static one.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
There are 2 total unresolved issues (including 1 from previous review).
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit b07a33a. Configure here.
|
|
||
| const userId = data.session.user.id | ||
| const headers = SUPABASE_AUTH_HEADERS(data.session.access_token, teamId) | ||
| const headers = authHeaders(data.session.access_token, teamId) |
There was a problem hiding this comment.
Ory client sandbox auth broken
Medium Severity
In Ory mode the proxy treats req.auth as authenticated, but oryAuthProvider.getAuthContext() returns null when the Auth.js session carries session.error (for example after RefreshTokenError). Users can pass middleware yet server routes and actions see no auth context.
Additional Locations (2)
Reviewed by Cursor Bugbot for commit b07a33a. Configure here.


Summary
AUTH_PROVIDERswitch.Test plan
bun run tsc --noEmitbun test tests/unit/auth-headers.test.ts tests/unit/auth-ory-provider.test.ts tests/integration/auth-ory-bootstrap.test.ts