fix(sso): hand the issuer /auth/callback so the code gets redeemed

The auth guard asked the issuer to come back to the page the visitor wanted.
The issuer appends ?code= to whatever URL it is given, but only /auth/callback
redeems a code — so it arrived on the target page, sat there unread, the guard
found no session and bounced back for a fresh code. The browser ping-ponged
between the two hosts until the user gave up.

The guard now hands over /auth/callback and carries the wanted page in its
redirect parameter, which is exactly what that page already expected. A spent
code in the URL is dropped rather than carried along, so a stale link cannot
turn into a redemption error one hop later.

URL building moved into shared/utils/ssoHandoff.ts because the same mistake
existed twice — the retry button on the callback page had it too — and because
a redirect loop deserves a regression test that does not need a browser.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
itsrubberduck
2026-07-28 19:14:20 +02:00
parent d397e95b9d
commit 56d3c991d7
4 changed files with 100 additions and 6 deletions

View File

@@ -1,5 +1,6 @@
import { defineNuxtRouteMiddleware, navigateTo, useRuntimeConfig } from '#app'
import { useAuthStore } from '~/stores/auth'
import { buildIssuerLoginUrl } from '~~/shared/utils/ssoHandoff'
export default defineNuxtRouteMiddleware(async (to) => {
const config = useRuntimeConfig()
@@ -31,10 +32,7 @@ export default defineNuxtRouteMiddleware(async (to) => {
return navigateTo(`/login?redirect=${encodeURIComponent(target)}`)
}
// The issuer needs an absolute URL to come back to, and it will only accept
// one whose origin is on its allowlist.
const redirect = new URL(target, window.location.origin).toString()
return navigateTo(`${issuer}/login?redirect=${encodeURIComponent(redirect)}`, {
return navigateTo(buildIssuerLoginUrl(issuer, window.location.origin, target), {
external: true,
})
})