Skip to content

Commit 0bdf789

Browse files
committed
Detect if cross-site cookie config is needed
Necessary if API_URL does not contain APP_URL.
1 parent 8078d80 commit 0bdf789

File tree

4 files changed

+27
-14
lines changed

4 files changed

+27
-14
lines changed

packages/api/src/auth/index.ts

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { Adapter, DatabaseSessionAttributes, DatabaseUserAttributes, Lucia, Time
22
import { DrizzleSQLiteAdapter } from '@lucia-auth/adapter-drizzle'
33
import { SessionTable, UserTable } from '../db/schema'
44
import { DB } from '../db/client'
5+
import type { ApiContextProps } from '../context'
56

67
/**
78
* Lucia's isValidRequestOrigin method will compare the
@@ -18,17 +19,30 @@ export const getAllowedOriginHost = (app_url: string, request?: Request) => {
1819
return requestHost === appHost ? appHost : undefined
1920
}
2021

21-
export const createAuth = (db: DB, appUrl: string) => {
22+
export const isCrossDomain = (appUrl?: string, apiUrl?: string) => {
23+
if (!appUrl || !apiUrl) return true
24+
const appHost = new URL(appUrl).host
25+
const apiHost = new URL(apiUrl).host
26+
return !apiHost.endsWith(appHost)
27+
}
28+
29+
export function getCookieOptions(ctx: ApiContextProps) {
30+
return isCrossDomain(ctx.env.APP_URL, ctx.env.PUBLIC_API_URL)
31+
? 'HttpOnly; SameSite=None; Secure;'
32+
: 'HttpOnly; SameSite=Lax; Secure;'
33+
}
34+
35+
export const createAuth = (db: DB, appUrl: string, apiUrl: string) => {
2236
// @ts-ignore Expect type errors because this is D1 and not SQLite... but it works
2337
const adapter = new DrizzleSQLiteAdapter(db, SessionTable, UserTable)
2438
// cast probably only needed until adapter-drizzle is updated
39+
// @ts-ignore the "none" option for sameSite works... but https://github.com/lucia-auth/lucia/issues/1320
2540
return new Lucia(adapter as Adapter, {
26-
...getAuthOptions(appUrl),
41+
...getAuthOptions(appUrl, apiUrl),
2742
})
2843
}
2944

30-
export const getAuthOptions = (appUrl: string) => {
31-
const env = !appUrl || appUrl.startsWith('http:') ? 'DEV' : 'PROD'
45+
export const getAuthOptions = (appUrl: string, apiUrl: string) => {
3246
return {
3347
getUserAttributes: (data: DatabaseUserAttributes) => {
3448
return {
@@ -45,8 +59,9 @@ export const getAuthOptions = (appUrl: string) => {
4559
name: 'auth_session',
4660
expires: false,
4761
attributes: {
48-
secure: env === 'PROD',
49-
sameSite: 'lax' as const,
62+
secure: true,
63+
// This might not work forever https://github.com/lucia-auth/lucia/issues/1320
64+
sameSite: isCrossDomain(appUrl, apiUrl) ? ('none' as const) : ('lax' as const),
5065
},
5166
},
5267

packages/api/src/auth/oauth.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import { createAuthMethodId, createUser, getAuthMethod, getUserById } from './us
2626
import { P, match } from 'ts-pattern'
2727
import { getCookie } from 'hono/cookie'
2828
import { TRPCError } from '@trpc/server'
29+
import { getCookieOptions, isCrossDomain } from '.'
2930

3031
export interface AppleIdTokenClaims {
3132
iss: 'https://appleid.apple.com'
@@ -101,15 +102,13 @@ export const getAuthorizationUrl = async (ctx: ApiContextProps, service: AuthPro
101102
const provider = getAuthProvider(ctx, service)
102103
const secure = ctx.req?.url.startsWith('https:') ? 'Secure; ' : ''
103104
const state = generateState()
104-
ctx.setCookie(
105-
`${service}_oauth_state=${state}; Path=/; ${secure}HttpOnly; SameSite=Lax; Max-Age=600`
106-
)
105+
ctx.setCookie(`${service}_oauth_state=${state}; Path=/; ${getCookieOptions(ctx)} Max-Age=600`)
107106
return await match({ provider, service })
108107
.with({ service: 'google', provider: P.instanceOf(Google) }, async ({ provider }) => {
109108
// Google requires PKCE
110109
const codeVerifier = generateCodeVerifier()
111110
ctx.setCookie(
112-
`${service}_oauth_verifier=${codeVerifier}; Path=/; ${secure}HttpOnly; SameSite=Lax; Max-Age=600`
111+
`${service}_oauth_verifier=${codeVerifier}; Path=/; ${getCookieOptions(ctx)} Max-Age=600`
113112
)
114113
const url = await provider.createAuthorizationURL(state, codeVerifier, {
115114
scopes: ['https://www.googleapis.com/auth/userinfo.email'],

packages/api/src/context.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ export const createContext = async (
6464

6565
// const user = await getUser()
6666

67-
const auth = createAuth(db, env.APP_URL)
67+
const auth = createAuth(db, env.APP_URL, env.PUBLIC_API_URL)
6868
const enableTokens = Boolean(context.req.header('x-enable-tokens'))
6969

7070
async function getSession() {

packages/api/src/routes/user.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import { getCookie } from 'hono/cookie'
3232
import { parseJWT } from 'oslo/jwt'
3333
import { P, match } from 'ts-pattern'
3434
import { AuthProviderName } from '../auth/providers'
35+
import { getCookieOptions } from '../auth'
3536

3637
export function sanitizeUserIdInput<K extends keyof T, T>({
3738
ctx,
@@ -229,9 +230,7 @@ const authorizationUrlHandler =
229230
}
230231
const secure = ctx.req?.url.startsWith('https:') ? 'Secure; ' : ''
231232
ctx.setCookie(
232-
`${input.provider}_oauth_redirect=${
233-
input.redirectTo || ''
234-
}; Path=/; ${secure}HttpOnly; SameSite=Lax`
233+
`${input.provider}_oauth_redirect=${input.redirectTo || ''}; Path=/; ${getCookieOptions(ctx)}`
235234
)
236235
return { redirectTo: url.toString() }
237236
}

0 commit comments

Comments
 (0)