Skip to content

Commit

Permalink
cross-domain cookies just don't work a lot of the time, switch to pro…
Browse files Browse the repository at this point in the history
…xied worker
  • Loading branch information
rmarscher committed Jan 6, 2024
1 parent 5e52741 commit f08abae
Show file tree
Hide file tree
Showing 8 changed files with 36 additions and 28 deletions.
11 changes: 11 additions & 0 deletions apps/next/next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,17 @@ module.exports = function () {
compiler: {
removeConsole: disableBrowserLogs,
},
async rewrites() {
return [
{
source: '/worker/:path*',
destination: process.env.NEXT_PUBLIC_API_URL
? `${process.env.NEXT_PUBLIC_API_URL}/worker/:path*`
: 'http://localhost:8787/worker/:path*',
},
]
},

}

for (const plugin of plugins) {
Expand Down
28 changes: 12 additions & 16 deletions packages/api/src/auth/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,16 @@ import { SessionTable, UserTable } from '../db/schema'
import { DB } from '../db/client'
import type { ApiContextProps } from '../context'

export const getRequestOrigin = (request?: Request) => {
if (!request) return undefined
const originHeader = request.headers.get('origin')
const forwardedProtoHeader = request.headers.get('x-forwarded-proto')
const forwardedHostHeader = request.headers.get('x-forwarded-host')
return originHeader || (forwardedProtoHeader && forwardedHostHeader)
? `${forwardedProtoHeader}://${forwardedHostHeader}`
: undefined
}

/**
* Lucia's isValidRequestOrigin method will compare the
* origin of the request to the configured host.
Expand All @@ -13,25 +23,12 @@ import type { ApiContextProps } from '../context'
*/
export const getAllowedOriginHost = (app_url: string, request?: Request) => {
if (!app_url || !request) return undefined
const requestOrigin = request.headers.get('Origin')
const requestOrigin = getRequestOrigin(request)
const requestHost = requestOrigin ? new URL(requestOrigin).host : undefined
const appHost = new URL(app_url).host
return requestHost === appHost ? appHost : undefined
}

export const isCrossDomain = (appUrl?: string, apiUrl?: string) => {
if (!appUrl || !apiUrl) return true
const appHost = new URL(appUrl).host
const apiHost = new URL(apiUrl).host
return !apiHost.endsWith(appHost)
}

export function getCookieOptions(ctx: ApiContextProps) {
return isCrossDomain(ctx.env.APP_URL, ctx.env.PUBLIC_API_URL)
? 'HttpOnly; SameSite=None; Secure;'
: 'HttpOnly; SameSite=Lax; Secure;'
}

export const createAuth = (db: DB, appUrl: string, apiUrl: string) => {
// @ts-ignore Expect type errors because this is D1 and not SQLite... but it works
const adapter = new DrizzleSQLiteAdapter(db, SessionTable, UserTable)
Expand Down Expand Up @@ -60,8 +57,7 @@ export const getAuthOptions = (appUrl: string, apiUrl: string) => {
expires: false,
attributes: {
secure: true,
// This might not work forever https://github.com/lucia-auth/lucia/issues/1320
sameSite: isCrossDomain(appUrl, apiUrl) ? ('none' as const) : ('lax' as const),
sameSite: 'lax' as const,
},
},

Expand Down
9 changes: 4 additions & 5 deletions packages/api/src/auth/oauth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,9 @@ import {
import { isWithinExpirationDate } from 'oslo'
import { parseJWT } from 'oslo/jwt'
import { createAuthMethodId, createUser, getAuthMethod, getUserById } from './user'

import { P, match } from 'ts-pattern'
import { getCookie } from 'hono/cookie'
import { TRPCError } from '@trpc/server'
import { getCookieOptions, isCrossDomain } from '.'

export interface AppleIdTokenClaims {
iss: 'https://appleid.apple.com'
Expand Down Expand Up @@ -100,15 +98,16 @@ export function getAppleClaims(idToken?: string): AppleIdTokenClaims | undefined

export const getAuthorizationUrl = async (ctx: ApiContextProps, service: AuthProviderName) => {
const provider = getAuthProvider(ctx, service)
const secure = ctx.req?.url.startsWith('https:') ? 'Secure; ' : ''
const state = generateState()
ctx.setCookie(`${service}_oauth_state=${state}; Path=/; ${getCookieOptions(ctx)} Max-Age=600`)
ctx.setCookie(
`${service}_oauth_state=${state}; Path=/; HttpOnly; SameSite=lax; Secure; Max-Age=600`
)
return await match({ provider, service })
.with({ service: 'google', provider: P.instanceOf(Google) }, async ({ provider }) => {
// Google requires PKCE
const codeVerifier = generateCodeVerifier()
ctx.setCookie(
`${service}_oauth_verifier=${codeVerifier}; Path=/; ${getCookieOptions(ctx)} Max-Age=600`
`${service}_oauth_verifier=${codeVerifier}; Path=/; HttpOnly; SameSite=lax; Secure; Max-Age=600`
)
const url = await provider.createAuthorizationURL(state, codeVerifier, {
scopes: ['https://www.googleapis.com/auth/userinfo.email'],
Expand Down
4 changes: 2 additions & 2 deletions packages/api/src/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import type { Context as HonoContext, HonoRequest } from 'hono'
import type { Lucia } from 'lucia'
import { verifyRequestOrigin } from 'oslo/request'
import { verifyToken } from './utils/crypto'
import { createAuth, getAllowedOriginHost } from './auth'
import { createAuth, getAllowedOriginHost, getRequestOrigin } from './auth'
import { getCookie } from 'hono/cookie'

export interface ApiContextProps {
Expand Down Expand Up @@ -84,7 +84,7 @@ export const createContext = async (

let authResult: Awaited<ReturnType<typeof auth.validateSession>> | undefined
if (cookieSessionId && !enableTokens) {
const originHeader = context.req.header('origin')
const originHeader = getRequestOrigin(context.req.raw)
const hostHeader = context.req.header('host')
const allowedOrigin = getAllowedOriginHost(context.env.APP_URL, context.req.raw)
if (
Expand Down
5 changes: 3 additions & 2 deletions packages/api/src/routes/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ import { getCookie } from 'hono/cookie'
import { parseJWT } from 'oslo/jwt'
import { P, match } from 'ts-pattern'
import { AuthProviderName } from '../auth/providers'
import { getCookieOptions } from '../auth'

export function sanitizeUserIdInput<K extends keyof T, T>({
ctx,
Expand Down Expand Up @@ -230,7 +229,9 @@ const authorizationUrlHandler =
}
const secure = ctx.req?.url.startsWith('https:') ? 'Secure; ' : ''
ctx.setCookie(
`${input.provider}_oauth_redirect=${input.redirectTo || ''}; Path=/; ${getCookieOptions(ctx)}`
`${input.provider}_oauth_redirect=${
input.redirectTo || ''
}; Path=/; HttpOnly; SameSite=lax; Secure`
)
return { redirectTo: url.toString() }
}
Expand Down
3 changes: 2 additions & 1 deletion packages/api/src/worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export type Bindings = Env & {
[k: string]: unknown
}

const app = new Hono<{ Bindings: Bindings }>()
const app = new Hono<{ Bindings: Bindings }>().basePath('/worker')

const corsHandler = async (c: Context<{ Bindings: Bindings }>, next: Next) => {
if (c.env.APP_URL === undefined) {
Expand All @@ -47,6 +47,7 @@ app.use('/trpc/*', corsHandler)
// Setup TRPC server with context
app.use('/trpc/*', async (c, next) => {
return await trpcServer({
endpoint: '/worker/trpc',
router: appRouter,
createContext: async ({ resHeaders }) => {
return await createContext(c.env, c, resHeaders)
Expand Down
2 changes: 1 addition & 1 deletion packages/app/utils/trpc/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export const TRPCProvider: React.FC<{
'x-enable-tokens': 'true',
}
},
url: `${getApiUrl()}/trpc`,
url: `${getApiUrl()}/worker/trpc`,
}),
],
})
Expand Down
2 changes: 1 addition & 1 deletion packages/app/utils/trpc/index.web.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ export function TRPCProvider(props: { children: React.ReactNode }) {
// always try to include cookies
})
},
url: `${process.env.NEXT_PUBLIC_API_URL}/trpc`,
url: `${typeof window !== 'undefined' ? '' : process.env.NEXT_PUBLIC_API_URL}/worker/trpc`,
}),
],
})
Expand Down

1 comment on commit f08abae

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✔ EAS production build completed

  • 🤖 Android build failed ❌
  • 🍏 IOS build failed ❌
Android QR IOS QR

Please sign in to comment.