Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .changeset/proxy-no-duplicate-auth.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
'@interline-io/tlv2-auth': patch
---

Fix authenticated proxy requests being rejected by the backend with `token is malformed: token contains an invalid number of segments` (HTTP 401).

The `auth.server` plugin injected `Authorization`/`apikey` onto backend requests with `Headers.append()`. Because the server-side proxy already attaches those headers, the request — which flows through the patched `globalThis.fetch` — had them appended a second time, comma-joining into `Authorization: "Bearer <tok>, Bearer <tok>"`. Switched both injection paths (`$fetch` `onRequest` and the `globalThis.fetch` override) to `Headers.set()`, which is idempotent.

Also stop forwarding the browser session cookie to the backend API in the proxy: it's irrelevant to the API and the encrypted auth0-nuxt session cookie duplicates the JWT.
12 changes: 10 additions & 2 deletions src/runtime/plugins/auth.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,12 @@ const plugin: Plugin = defineNuxtPlugin((nuxtApp) => {
if (!isBackendRequest(effectiveUrl)) { return }
const authHeaders = await getAuthHeaders()
const headers = new Headers(options.headers || {})
// set() not append(): a request may already carry Authorization/apikey
// (e.g. the server-side proxy pre-authenticates its outbound request).
// append() would comma-join into "Bearer <tok>, Bearer <tok>", which the
// backend parses as a malformed JWT and rejects with 401.
for (const [key, value] of Object.entries(authHeaders)) {
headers.append(key, value)
headers.set(key, value)
}
Comment on lines 68 to 76
options.headers = headers
}
Expand All @@ -84,8 +88,12 @@ const plugin: Plugin = defineNuxtPlugin((nuxtApp) => {
const authHeaders = await getAuthHeaders()
init = init || {}
const headers = new Headers(init.headers || {})
// set() not append(): the request may already carry Authorization/apikey
// (the server-side proxy pre-authenticates its outbound request, which
// flows through this override). append() would comma-join into
// "Bearer <tok>, Bearer <tok>", which the backend rejects as a malformed JWT.
for (const [key, value] of Object.entries(authHeaders)) {
headers.append(key, value)
headers.set(key, value)
}
init.headers = headers
return originalFetch(input, init)
Expand Down
6 changes: 6 additions & 0 deletions src/runtime/util/proxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@ export async function proxyHandler (
const query = getQuery(event)
const requestApikey = (query.apikey ? query.apikey.toString() : '') || event.headers.get('apikey') || ''
const headers = buildProxyHeaders(graphqlApikey, accessToken, requestApikey)
// Never forward the browser session cookie to the backend API. It's
// irrelevant to the API, and the encrypted auth0-nuxt session cookie
// effectively duplicates the JWT we already attach. h3's mergeHeaders treats
// an empty string as an override (undefined would be ignored), so this
// replaces the forwarded Cookie rather than leaving it intact.
headers.cookie = ''
const target = buildProxyTarget(proxyBase, pathOverride ?? event.path)

if (traceEnabled) {
Expand Down