Skip to content

Commit 3d6ba32

Browse files
committed
feat: support self-hosted ory via kratos admin url
1 parent 4af7723 commit 3d6ba32

4 files changed

Lines changed: 69 additions & 12 deletions

File tree

.env.example

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@ NEXT_PUBLIC_SUPABASE_ANON_KEY=your_supabase_anon_key
3434
# ORY_OAUTH2_AUDIENCE=https://api.e2b.dev
3535
### Ory project admin API token used by oryAuthAdmin (IdentityApi lookups)
3636
# ORY_PROJECT_API_TOKEN=
37+
### Self-hosted Ory admin endpoints (alternative to ORY_PROJECT_API_TOKEN).
38+
### Set both when running self-hosted; leave unset to use Ory Network with the PAT above.
39+
# ORY_KRATOS_ADMIN_URL=http://localhost:4434
40+
# ORY_HYDRA_ADMIN_URL=http://localhost:4445
3741
### Dashboard API admin token used to bootstrap newly signed-in Ory users
3842
# DASHBOARD_API_ADMIN_TOKEN=
3943

scripts/check-app-env.ts

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,34 @@ import { clientSchema, serverSchema, validateEnv } from '../src/lib/env'
55
const projectDir = process.cwd()
66
loadEnvConfig(projectDir)
77

8+
// Always required when AUTH_PROVIDER=ory, regardless of deploy target.
89
const oryRequiredEnvVars = [
910
'AUTH_SECRET',
1011
'ORY_SDK_URL',
1112
'ORY_OAUTH2_CLIENT_ID',
1213
'ORY_OAUTH2_CLIENT_SECRET',
1314
'ORY_OAUTH2_AUDIENCE',
14-
'ORY_PROJECT_API_TOKEN',
1515
'DASHBOARD_API_ADMIN_TOKEN',
1616
] as const
1717

18+
// Identity admin surface (Kratos): pick exactly one.
19+
// - ORY_PROJECT_API_TOKEN: Ory Network. Bearer for the unified SDK host.
20+
// - ORY_KRATOS_ADMIN_URL: self-hosted Kratos admin (gated by network).
21+
// At least one must be set so IdentityApi calls can resolve.
22+
const oryIdentityAdminEnvVars = [
23+
'ORY_PROJECT_API_TOKEN',
24+
'ORY_KRATOS_ADMIN_URL',
25+
] as const
26+
27+
// OAuth2 admin surface (Hydra): pick exactly one.
28+
// - ORY_PROJECT_API_TOKEN: Ory Network. Bearer for the unified SDK host.
29+
// - ORY_HYDRA_ADMIN_URL: self-hosted Hydra admin (gated by network).
30+
// At least one must be set so OAuth2Api session revocations can resolve.
31+
const oryOAuth2AdminEnvVars = [
32+
'ORY_PROJECT_API_TOKEN',
33+
'ORY_HYDRA_ADMIN_URL',
34+
] as const
35+
1836
const schema = serverSchema
1937
.merge(clientSchema)
2038
.refine(
@@ -81,6 +99,28 @@ const schema = serverSchema
8199
path: ['AUTH_PROVIDER'],
82100
})
83101
}
102+
103+
const hasIdentityAdmin = oryIdentityAdminEnvVars.some(
104+
(envVar) => !!data[envVar]
105+
)
106+
if (!hasIdentityAdmin) {
107+
ctx.addIssue({
108+
code: z.ZodIssueCode.custom,
109+
message: `AUTH_PROVIDER=ory requires either ${oryIdentityAdminEnvVars.join(' (Ory Network) or ')} (self-hosted Kratos admin)`,
110+
path: ['AUTH_PROVIDER'],
111+
})
112+
}
113+
114+
const hasOAuth2Admin = oryOAuth2AdminEnvVars.some(
115+
(envVar) => !!data[envVar]
116+
)
117+
if (!hasOAuth2Admin) {
118+
ctx.addIssue({
119+
code: z.ZodIssueCode.custom,
120+
message: `AUTH_PROVIDER=ory requires either ${oryOAuth2AdminEnvVars.join(' (Ory Network) or ')} (self-hosted Hydra admin)`,
121+
path: ['AUTH_PROVIDER'],
122+
})
123+
}
84124
})
85125

86126
validateEnv(schema)

src/core/server/auth/ory/client.ts

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,35 +5,46 @@ import { Configuration, IdentityApi, OAuth2Api } from '@ory/client-fetch'
55
let cachedIdentityApi: IdentityApi | null = null
66
let cachedOAuth2Api: OAuth2Api | null = null
77

8-
// the IdentityApi requires the Ory project admin token (PAT). callers should
9-
// ensure ORY_PROJECT_API_TOKEN is set at deploy time when AUTH_PROVIDER=ory.
8+
// IdentityApi resolution:
9+
// 1. ORY_KRATOS_ADMIN_URL — self-hosted Kratos admin (e.g. local devenv :4434).
10+
// 2. ORY_SDK_URL — Ory Network (identity admin co-located on the SDK host).
11+
//
12+
// OAuth2Api resolution:
13+
// 1. ORY_HYDRA_ADMIN_URL — self-hosted Hydra admin (e.g. local devenv :4445).
14+
// 2. ORY_SDK_URL — Ory Network (OAuth2 admin co-located on the SDK host).
15+
//
16+
// The PAT is attached only when configured: Ory Network gates on it,
17+
// self-hosted admin surfaces are gated by network reachability instead.
1018
export function getOryIdentityApi(): IdentityApi {
1119
if (cachedIdentityApi) return cachedIdentityApi
1220

13-
cachedIdentityApi = new IdentityApi(getOryConfiguration())
21+
cachedIdentityApi = new IdentityApi(
22+
getOryConfiguration(process.env.ORY_KRATOS_ADMIN_URL)
23+
)
24+
1425
return cachedIdentityApi
1526
}
1627

1728
export function getOryOAuth2Api(): OAuth2Api {
1829
if (cachedOAuth2Api) return cachedOAuth2Api
1930

20-
cachedOAuth2Api = new OAuth2Api(getOryConfiguration())
31+
cachedOAuth2Api = new OAuth2Api(
32+
getOryConfiguration(process.env.ORY_HYDRA_ADMIN_URL)
33+
)
2134
return cachedOAuth2Api
2235
}
2336

24-
function getOryConfiguration(): Configuration {
25-
const basePath = process.env.ORY_SDK_URL
26-
const accessToken = process.env.ORY_PROJECT_API_TOKEN
37+
function getOryConfiguration(basePathOverride?: string): Configuration {
38+
const basePath = basePathOverride ?? process.env.ORY_SDK_URL
2739

2840
if (!basePath) {
2941
throw new Error('ORY_SDK_URL is not configured')
3042
}
31-
if (!accessToken) {
32-
throw new Error('ORY_PROJECT_API_TOKEN is not configured')
33-
}
43+
44+
const accessToken = process.env.ORY_PROJECT_API_TOKEN
3445

3546
return new Configuration({
3647
basePath: basePath.replace(/\/$/, ''),
37-
accessToken,
48+
...(accessToken ? { accessToken } : {}),
3849
})
3950
}

src/lib/env.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ export const serverSchema = z.object({
2222
ORY_OAUTH2_CLIENT_SECRET: z.string().min(1).optional(),
2323
ORY_OAUTH2_AUDIENCE: z.string().min(1).optional(),
2424
ORY_PROJECT_API_TOKEN: z.string().min(1).optional(),
25+
ORY_KRATOS_ADMIN_URL: z.url().optional(),
26+
ORY_HYDRA_ADMIN_URL: z.url().optional(),
2527

2628
OTEL_SERVICE_NAME: z.string().optional(),
2729
OTEL_EXPORTER_OTLP_ENDPOINT: z.url().optional(),

0 commit comments

Comments
 (0)