Skip to content

Commit f084c01

Browse files
committed
feat: migrate to ory/elements and replace nextauth logic
1 parent 00d78bd commit f084c01

57 files changed

Lines changed: 2197 additions & 1487 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.env.example

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,22 +5,23 @@
55
### Dashboard API admin token used for user bootstrap and creator email hydration
66
DASHBOARD_API_ADMIN_TOKEN=your_dashboard_api_admin_token
77

8-
### Auth.js configuration
9-
### Generate with `npx auth secret` or `openssl rand -hex 32`.
10-
AUTH_SECRET=your_auth_secret
8+
### JWE key for the encrypted e2b_session cookie (carries the Hydra OIDC tokens).
9+
### Generate with `openssl rand -hex 32`.
10+
E2B_SESSION_SECRET=your_e2b_session_secret
1111

1212
### Ory Network configuration
1313
ORY_SDK_URL=https://your-project.projects.oryapis.com
14+
### OIDC issuer for the OAuth client. Falls back to ORY_SDK_URL on Ory Network;
15+
### set explicitly for self-hosted Hydra (e.g. http://localhost:4444).
16+
# ORY_HYDRA_PUBLIC_URL=http://localhost:4444
1417
ORY_OAUTH2_CLIENT_ID=your_ory_oauth2_client_id
1518
ORY_OAUTH2_CLIENT_SECRET=your_ory_oauth2_client_secret
1619
### Access-token audience requested from Ory. Must match the backend JWT audience configuration.
1720
ORY_OAUTH2_AUDIENCE=https://api.e2b.dev
1821
### Ory project admin API token used for IdentityApi lookups
1922
ORY_PROJECT_API_TOKEN=your_ory_project_api_token
2023

21-
### Custom Ory UI: "true" on Preview/Staging, unset on Production.
22-
# NEXT_PUBLIC_ORY_CUSTOM_UI=true
23-
### Kratos public URL for the custom UI (self-hosted :4433; falls back to ORY_SDK_URL).
24+
### Browser-facing Kratos public URL for the Elements UI (self-hosted :4433; falls back to ORY_SDK_URL).
2425
# NEXT_PUBLIC_ORY_SDK_URL=http://localhost:4433
2526

2627
### Domain for the E2B cluster
@@ -41,8 +42,6 @@ NEXT_PUBLIC_E2B_DOMAIN=e2b.dev
4142
### Set both when running self-hosted; leave unset to use Ory Network with the PAT above.
4243
# ORY_KRATOS_ADMIN_URL=http://localhost:4434
4344
# ORY_HYDRA_ADMIN_URL=http://localhost:4445
44-
### Set to 1 outside Vercel-hosted production to allow Auth.js to trust the Host header
45-
# AUTH_TRUST_HOST=1
4645

4746
# ENABLE_USER_BOOTSTRAP=0
4847

bun.lock

Lines changed: 3 additions & 11 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,14 +108,15 @@
108108
"fast-xml-parser": "^5.3.5",
109109
"immer": "^10.1.1",
110110
"input-otp": "^1.4.2",
111+
"jose": "^6.2.3",
111112
"micromatch": "^4.0.8",
112113
"motion": "^12.23.25",
113114
"nanoid": "^5.0.9",
114115
"next": "^16.2.7",
115-
"next-auth": "^5.0.0-beta.31",
116116
"next-safe-action": "^8.0.11",
117117
"next-themes": "^0.4.6",
118118
"nuqs": "^2.7.0",
119+
"oauth4webapi": "^3.8.6",
119120
"openapi-fetch": "^0.14.0",
120121
"pathe": "^2.0.3",
121122
"pino": "^9.7.0",

src/app/api/auth/oauth/[...nextauth]/route.ts

Lines changed: 0 additions & 5 deletions
This file was deleted.

src/app/api/auth/oauth/bootstrap-failed/route.ts

Lines changed: 0 additions & 66 deletions
This file was deleted.
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
import 'server-only'
2+
3+
import { type NextRequest, NextResponse } from 'next/server'
4+
import { PROTECTED_URLS } from '@/configs/urls'
5+
import { ensureOryUserBootstrapped } from '@/core/server/auth/ory/dashboard-bootstrap'
6+
import { exchangeOryCallback } from '@/core/server/auth/ory/oauth-client'
7+
import {
8+
E2B_OAUTH_FLOW_COOKIE,
9+
OAUTH_CALLBACK_PATH,
10+
parseOryFlowState,
11+
} from '@/core/server/auth/ory/oauth-flow'
12+
import {
13+
E2B_SESSION_COOKIE,
14+
orySessionCookieOptions,
15+
sealOrySession,
16+
} from '@/core/server/auth/ory/session-cookie'
17+
import {
18+
buildOryLogoutUrl,
19+
ORY_POST_LOGOUT_PATH,
20+
} from '@/core/server/auth/ory/signout'
21+
import { ORY_SIGNUP_METADATA_COOKIE } from '@/core/server/auth/ory/signup-metadata'
22+
import { l, serializeErrorForLog } from '@/core/shared/clients/logger/logger'
23+
24+
// Failures land on the recover route, whose one-shot guard retries the flow
25+
// once (via /sign-in → /start, which mints a fresh flow cookie) before bailing
26+
// to home — so a stale/invalid callback can't loop.
27+
const ORY_RECOVER_PATH = '/api/auth/oauth/recover'
28+
29+
// Hydra redirects here with ?code after Kratos created the session. We exchange
30+
// the code (validating state/nonce/PKCE), provision the dashboard user from the
31+
// id_token, then seal the OIDC tokens into e2b_session. Kratos already owns the
32+
// session at this point — this cookie only carries tokens for API access.
33+
export async function GET(request: NextRequest) {
34+
const origin = request.nextUrl.origin
35+
const flow = parseOryFlowState(
36+
request.cookies.get(E2B_OAUTH_FLOW_COOKIE)?.value
37+
)
38+
39+
if (!flow) {
40+
l.warn(
41+
{ key: 'oauth_callback:missing_flow_state' },
42+
'Ory callback hit without a valid flow-state cookie'
43+
)
44+
return finalize(NextResponse.redirect(new URL(ORY_RECOVER_PATH, origin)))
45+
}
46+
47+
let tokens: Awaited<ReturnType<typeof exchangeOryCallback>>
48+
try {
49+
tokens = await exchangeOryCallback({
50+
// A genuine global URL — oauth4webapi rejects NextURL (not `instanceof URL`).
51+
currentUrl: new URL(request.url),
52+
expectedState: flow.state,
53+
expectedNonce: flow.nonce,
54+
codeVerifier: flow.codeVerifier,
55+
redirectUri: new URL(OAUTH_CALLBACK_PATH, origin).toString(),
56+
})
57+
} catch (error) {
58+
l.error(
59+
{
60+
key: 'oauth_callback:exchange_failed',
61+
error: serializeErrorForLog(error),
62+
},
63+
'Ory authorization code exchange failed'
64+
)
65+
return finalize(NextResponse.redirect(new URL(ORY_RECOVER_PATH, origin)))
66+
}
67+
68+
const bootstrapped = await ensureOryUserBootstrapped({
69+
accessToken: tokens.accessToken,
70+
idToken: tokens.idToken,
71+
provider: 'ory',
72+
})
73+
74+
if (!bootstrapped) {
75+
l.error(
76+
{ key: 'oauth_callback:bootstrap_failed' },
77+
'dashboard bootstrap failed; ending the Ory session without a dashboard cookie'
78+
)
79+
// Don't strand the user with a half-provisioned login: end the Ory + Kratos
80+
// session via RP-logout (falling back to home if no id_token is available).
81+
const logoutUrl = tokens.idToken
82+
? buildOryLogoutUrl({ idToken: tokens.idToken, origin })
83+
: null
84+
return finalize(
85+
NextResponse.redirect(logoutUrl ?? new URL(ORY_POST_LOGOUT_PATH, origin))
86+
)
87+
}
88+
89+
const sealed = await sealOrySession({
90+
accessToken: tokens.accessToken,
91+
refreshToken: tokens.refreshToken,
92+
idToken: tokens.idToken,
93+
expiresAt: tokens.expiresAt,
94+
})
95+
96+
const destination = flow.returnTo ?? PROTECTED_URLS.DASHBOARD
97+
const response = finalize(NextResponse.redirect(new URL(destination, origin)))
98+
response.cookies.set(E2B_SESSION_COOKIE, sealed, orySessionCookieOptions())
99+
return response
100+
}
101+
102+
// Clears the one-shot transient cookies on every exit path.
103+
function finalize(response: NextResponse): NextResponse {
104+
response.cookies.delete(E2B_OAUTH_FLOW_COOKIE)
105+
response.cookies.delete(ORY_SIGNUP_METADATA_COOKIE)
106+
return response
107+
}

src/app/api/auth/oauth/recover/route.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,10 @@ export async function GET(request: NextRequest) {
1212

1313
l.error(
1414
{
15-
key: 'oauth_recover:auth_js_error',
15+
key: 'oauth_recover:flow_failed',
1616
context: { error_code: errorCode, already_attempted: alreadyAttempted },
1717
},
18-
'Auth.js OAuth flow failed; recovering user'
18+
'OAuth flow failed; recovering user once before bailing to home'
1919
)
2020

2121
const destination = alreadyAttempted ? '/' : AUTH_URLS.SIGN_IN

0 commit comments

Comments
 (0)