From e9a3e8ea527eb93621f75edbc650e1e31411346d Mon Sep 17 00:00:00 2001 From: Nico Domino Date: Thu, 14 Mar 2024 18:01:51 +0100 Subject: [PATCH 1/4] chore(jsdoc): note different default basePaths (#10239) --- packages/core/src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index db329be800..3b091f4f28 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -439,7 +439,7 @@ export interface AuthConfig { /** * The base path of the Auth.js API endpoints. * - * @default "/auth" + * @default "/api/auth" in "next-auth"; "/auth" with all other frameworks */ basePath?: string } From 5bca263d28fdc744e1b3f4c7d91ac1d74d88c4be Mon Sep 17 00:00:00 2001 From: Thang Vu Date: Sat, 16 Mar 2024 21:29:33 +0700 Subject: [PATCH 2/4] docs: add warning codes for env URL & basePath (#10322) --- docs/docs/reference/warnings.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/docs/reference/warnings.md b/docs/docs/reference/warnings.md index 5e3a868c35..71908d7a1e 100644 --- a/docs/docs/reference/warnings.md +++ b/docs/docs/reference/warnings.md @@ -12,3 +12,11 @@ The `debug` option was evaluated to `true`. It adds extra logs in the terminal w ## CSRF disabled You were trying to get a CSRF response from Auth.js (eg.: by calling a `/csrf` endpoint), but in this setup, CSRF protection via Auth.js was turned off. This is likely if you are not directly using `@auth/core` but a framework library (like `@auth/sveltekit`) that already has CSRF protection built-in. You likely won't need the CSRF response. + +## Env URL and basePath redundant + +`AUTH_URL` (or `NEXTAUTH_URL`) and `authConfig.basePath` are both declared. This is a configuration mistake - you should either remove the `authConfig.basePath` configuration, or remove the `pathname` of `AUTH_URL` (or `NEXTAUTH_URL`). Only one of them is needed. + +## Env URL basePath mismatch + +`AUTH_URL` (or `NEXTAUTH_URL`) and `authConfig.basePath` are both declared, but they don't match. This is a configuration mistake. `@auth/core` will use `basePath` to construct the full URL to the corresponding action (/signin, /signout, etc.) in this case. From 3c035ec62f2f21d7cab65504ba83fb1a9a13be01 Mon Sep 17 00:00:00 2001 From: Benjamin Wallberg Date: Sat, 16 Mar 2024 15:42:03 +0100 Subject: [PATCH 3/4] fix(next-auth): cannot parse action at /session (#10218) * fix(next-auth): cannot parse action at /session fix: support apps hosted on subpaths * refactor: use createActionURL from core directly --------- Co-authored-by: Nico Domino Co-authored-by: Thang Vu --- packages/next-auth/src/lib/actions.ts | 52 +++++++++++++-------------- packages/next-auth/src/lib/index.ts | 12 +++++-- 2 files changed, 34 insertions(+), 30 deletions(-) diff --git a/packages/next-auth/src/lib/actions.ts b/packages/next-auth/src/lib/actions.ts index 66934cf11f..c771acacbf 100644 --- a/packages/next-auth/src/lib/actions.ts +++ b/packages/next-auth/src/lib/actions.ts @@ -1,4 +1,4 @@ -import { Auth, raw, skipCSRFCheck } from "@auth/core" +import { Auth, raw, skipCSRFCheck, createActionURL } from "@auth/core" import { headers as nextHeaders, cookies } from "next/headers" import { redirect } from "next/navigation" @@ -23,7 +23,14 @@ export async function signIn( } = options instanceof FormData ? Object.fromEntries(options) : options const callbackUrl = redirectTo?.toString() ?? headers.get("Referer") ?? "/" - const signInURL = createActionURL("signin", headers, config.basePath) + const signInURL = createActionURL( + "signin", + // @ts-expect-error `x-forwarded-proto` is not nullable, next.js sets it by default + headers.get("x-forwarded-proto"), + headers, + process.env, + config.basePath + ) if (!provider) { signInURL.searchParams.append("callbackUrl", callbackUrl) @@ -78,7 +85,14 @@ export async function signOut( const headers = new Headers(nextHeaders()) headers.set("Content-Type", "application/x-www-form-urlencoded") - const url = createActionURL("signout", headers, config.basePath) + const url = createActionURL( + "signout", + // @ts-expect-error `x-forwarded-proto` is not nullable, next.js sets it by default + headers.get("x-forwarded-proto"), + headers, + process.env, + config.basePath + ) const callbackUrl = options?.redirectTo ?? headers.get("Referer") ?? "/" const body = new URLSearchParams({ callbackUrl }) const req = new Request(url, { method: "POST", headers, body }) @@ -100,7 +114,14 @@ export async function update( const headers = new Headers(nextHeaders()) headers.set("Content-Type", "application/json") - const url = createActionURL("session", headers, config.basePath) + const url = createActionURL( + "session", + // @ts-expect-error `x-forwarded-proto` is not nullable, next.js sets it by default + headers.get("x-forwarded-proto"), + headers, + process.env, + config.basePath + ) const body = JSON.stringify({ data }) const req = new Request(url, { method: "POST", headers, body }) @@ -110,26 +131,3 @@ export async function update( return res.body } - -/** - * Extract the origin and base path from either `AUTH_URL` or `NEXTAUTH_URL` environment variables, - * or the request's headers and the {@link NextAuthConfig.basePath} option. - */ -export function createActionURL( - action: AuthAction, - h: Headers | ReturnType, - basePath?: string -): URL { - const envUrl = process.env.AUTH_URL ?? process.env.NEXTAUTH_URL - if (envUrl) { - const { origin, pathname } = new URL(envUrl) - const separator = pathname.endsWith("/") ? "" : "/" - return new URL(`${origin}${pathname}${separator}${action}`) - } - const host = h.get("x-forwarded-host") ?? h.get("host") - const protocol = h.get("x-forwarded-proto") === "http" ? "http" : "https" - // @ts-expect-error `basePath` value is default'ed to "/api/auth" in `setEnvDefaults` - const { origin, pathname } = new URL(basePath, `${protocol}://${host}`) - const separator = pathname.endsWith("/") ? "" : "/" - return new URL(`${origin}${pathname}${separator}${action}`) -} diff --git a/packages/next-auth/src/lib/index.ts b/packages/next-auth/src/lib/index.ts index 1e1d95458d..df40ba594b 100644 --- a/packages/next-auth/src/lib/index.ts +++ b/packages/next-auth/src/lib/index.ts @@ -1,8 +1,7 @@ -import { Auth, type AuthConfig } from "@auth/core" +import { Auth, createActionURL, type AuthConfig } from "@auth/core" import { headers } from "next/headers" import { NextResponse } from "next/server" import { reqWithEnvURL } from "./env.js" -import { createActionURL } from "./actions.js" import type { AuthAction, Awaitable, Session } from "@auth/core/types" import type { @@ -60,7 +59,14 @@ export interface NextAuthConfig extends Omit { } async function getSession(headers: Headers, config: NextAuthConfig) { - const url = createActionURL("session", headers, config.basePath) + const url = createActionURL( + "session", + // @ts-expect-error `x-forwarded-proto` is not nullable, next.js sets it by default + headers.get("x-forwarded-proto"), + headers, + process.env, + config.basePath + ) const request = new Request(url, { headers: { cookie: headers.get("cookie") ?? "" }, }) From 5ea8b7b0f4d285e48f141dd91e518c905c9fb34e Mon Sep 17 00:00:00 2001 From: Benjamin Wallberg Date: Sat, 16 Mar 2024 16:04:45 +0100 Subject: [PATCH 4/4] fix(sveltekit): cannot parse action at /session (#10219) * fix(svelte): cannot parse action at /session fix: support apps hosted on subpaths * Apply suggestions from code review * Apply suggestions from code review --------- Co-authored-by: Nico Domino Co-authored-by: Thang Vu --- .../frameworks-sveltekit/src/lib/actions.ts | 40 ++++++------------- 1 file changed, 13 insertions(+), 27 deletions(-) diff --git a/packages/frameworks-sveltekit/src/lib/actions.ts b/packages/frameworks-sveltekit/src/lib/actions.ts index 4d661ae264..6b5e7d68ab 100644 --- a/packages/frameworks-sveltekit/src/lib/actions.ts +++ b/packages/frameworks-sveltekit/src/lib/actions.ts @@ -1,11 +1,9 @@ import { redirect } from "@sveltejs/kit" import type { RequestEvent } from "@sveltejs/kit" import { parse } from "set-cookie-parser" -import { dev } from "$app/environment" import { env } from "$env/dynamic/private" -import { Auth, raw, skipCSRFCheck } from "@auth/core" -import type { AuthAction } from "@auth/core/types" +import { Auth, createActionURL, raw, skipCSRFCheck } from "@auth/core" import type { SvelteKitAuthConfig } from "./types" import { setEnvDefaults } from "./env" @@ -17,7 +15,7 @@ export async function signIn( config: SvelteKitAuthConfig, event: RequestEvent ) { - const { request } = event + const { request, url: { protocol } } = event const headers = new Headers(request.headers) const { redirect: shouldRedirect = true, @@ -26,7 +24,13 @@ export async function signIn( } = options instanceof FormData ? Object.fromEntries(options) : options const callbackUrl = redirectTo?.toString() ?? headers.get("Referer") ?? "/" - const base = createActionURL("signin", headers, config.basePath) + const base = createActionURL( + "signin", + protocol, + headers, + env, + config.basePath + ) if (!provider) { const url = `${base}?${new URLSearchParams({ callbackUrl })}` @@ -78,11 +82,11 @@ export async function signOut( config: SvelteKitAuthConfig, event: RequestEvent ) { - const { request } = event + const { request, url: { protocol } } = event const headers = new Headers(request.headers) headers.set("Content-Type", "application/x-www-form-urlencoded") - const url = createActionURL("signout", headers, config.basePath) + const url = createActionURL("signout", protocol, headers, env, config.basePath) const callbackUrl = options?.redirectTo ?? headers.get("Referer") ?? "/" const body = new URLSearchParams({ callbackUrl }) const req = new Request(url, { method: "POST", headers, body }) @@ -105,9 +109,9 @@ export async function auth( setEnvDefaults(env, config) config.trustHost ??= true - const { request: req } = event + const { request: req, url: { protocol } } = event - const sessionUrl = createActionURL("session", req.headers, config.basePath) + const sessionUrl = createActionURL("session", protocol, req.headers, env, config.basePath) const request = new Request(sessionUrl, { headers: { cookie: req.headers.get("cookie") ?? "" }, }) @@ -127,21 +131,3 @@ export async function auth( if (status === 200) return data throw new Error(data.message) } - -/** - * Extract the origin and base path from either `AUTH_URL` or `NEXTAUTH_URL` environment variables, - * or the request's headers and the {@link NextAuthConfig.basePath} option. - */ -export function createActionURL( - action: AuthAction, - headers: Headers, - basePath?: string -) { - let url = env.AUTH_URL - if (!url) { - const host = headers.get("x-forwarded-host") ?? headers.get("host") - const proto = headers.get("x-forwarded-proto") - url = `${proto === "http" || dev ? "http" : "https"}://${host}${basePath}` - } - return new URL(`${url.replace(/\/$/, "")}/${action}`) -}