diff --git a/src/server/auth-client.test.ts b/src/server/auth-client.test.ts index 7395ad813..9850574b9 100644 --- a/src/server/auth-client.test.ts +++ b/src/server/auth-client.test.ts @@ -806,6 +806,42 @@ ca/T0LLtgmbMmxSv/MmzIg== ); }); + it("should configure redirect_uri when appBaseUrl isnt the root", async () => { + const secret = await generateSecret(32); + const transactionStore = new TransactionStore({ + secret + }); + const sessionStore = new StatelessSessionStore({ + secret + }); + const authClient = new AuthClient({ + transactionStore, + sessionStore, + + domain: DEFAULT.domain, + clientId: DEFAULT.clientId, + clientSecret: DEFAULT.clientSecret, + + secret, + appBaseUrl: `${DEFAULT.appBaseUrl}/sub-path`, + + fetch: getMockAuthorizationServer() + }); + const request = new NextRequest( + new URL("/auth/login", DEFAULT.appBaseUrl), + { + method: "GET" + } + ); + + const response = await authClient.handleLogin(request); + const authorizationUrl = new URL(response.headers.get("Location")!); + + expect(authorizationUrl.searchParams.get("redirect_uri")).toEqual( + `${DEFAULT.appBaseUrl}/sub-path/auth/callback` + ); + }); + it("should return an error if the discovery endpoint could not be fetched", async () => { const secret = await generateSecret(32); const transactionStore = new TransactionStore({ diff --git a/src/server/auth-client.ts b/src/server/auth-client.ts index 4664cc9ea..a7e625a15 100644 --- a/src/server/auth-client.ts +++ b/src/server/auth-client.ts @@ -107,6 +107,18 @@ export interface AuthClientOptions { enableTelemetry?: boolean; } +function ensureTrailingSlash(value: string) { + return value && !value.endsWith('/') ? `${value}/` : value; +} + +function ensureNoLeadingSlash(value: string) { + return value && value.startsWith('/') ? value.substring(1, value.length) : value; +} + +function createRouteUrl(url: string, base: string) { + return new URL(ensureNoLeadingSlash(url), ensureTrailingSlash(base)); +} + export class AuthClient { private transactionStore: TransactionStore; private sessionStore: AbstractSessionStore; @@ -261,7 +273,7 @@ export class AuthClient { } async handleLogin(req: NextRequest): Promise { - const redirectUri = new URL(this.routes.callback, this.appBaseUrl); // must be registed with the authorization server + const redirectUri = createRouteUrl(this.routes.callback, this.appBaseUrl); // must be registed with the authorization server const dangerousReturnTo = req.nextUrl.searchParams.get("returnTo"); let returnTo = this.signInReturnToPath; @@ -431,7 +443,7 @@ export class AuthClient { ); } - const redirectUri = new URL(this.routes.callback, this.appBaseUrl); // must be registed with the authorization server + const redirectUri = createRouteUrl(this.routes.callback, this.appBaseUrl); // must be registed with the authorization server const codeGrantResponse = await oauth.authorizationCodeGrantRequest( authorizationServerMetadata, this.clientMetadata, @@ -733,7 +745,7 @@ export class AuthClient { } const res = NextResponse.redirect( - new URL(ctx.returnTo || "/", this.appBaseUrl) + createRouteUrl(ctx.returnTo || "/", this.appBaseUrl) ); return res;