From 4e3465d81bd9e9ab73422bd02a821bf6c9ebd001 Mon Sep 17 00:00:00 2001 From: Jeff Escalante Date: Wed, 21 May 2025 22:31:19 -0400 Subject: [PATCH] add state param to oauth provider --- src/client/auth.test.ts | 25 +++++++++++++++++++++++++ src/client/auth.ts | 14 ++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/src/client/auth.test.ts b/src/client/auth.test.ts index eba7074b..97a10a3e 100644 --- a/src/client/auth.test.ts +++ b/src/client/auth.test.ts @@ -202,6 +202,31 @@ describe("OAuth Authorization", () => { expect(authorizationUrl.searchParams.has("scope")).toBe(false); }); + it("includes state parameter when provided", async () => { + const { authorizationUrl } = await startAuthorization( + "https://auth.example.com", + { + clientInformation: validClientInfo, + redirectUrl: "http://localhost:3000/callback", + state: "foobar", + } + ); + + expect(authorizationUrl.searchParams.get("state")).toBe("foobar"); + }); + + it("excludes state parameter when not provided", async () => { + const { authorizationUrl } = await startAuthorization( + "https://auth.example.com", + { + clientInformation: validClientInfo, + redirectUrl: "http://localhost:3000/callback", + } + ); + + expect(authorizationUrl.searchParams.has("state")).toBe(false); + }); + it("uses metadata authorization_endpoint when provided", async () => { const { authorizationUrl } = await startAuthorization( "https://auth.example.com", diff --git a/src/client/auth.ts b/src/client/auth.ts index e4941576..df97e5d7 100644 --- a/src/client/auth.ts +++ b/src/client/auth.ts @@ -21,6 +21,11 @@ export interface OAuthClientProvider { */ get clientMetadata(): OAuthClientMetadata; + /** + * Returns a OAuth2 state parameter. + */ + state?(): string | Promise; + /** * Loads information about this OAuth client, as registered already with the * server, or returns `undefined` if the client is not registered with the @@ -148,10 +153,13 @@ export async function auth( } } + const state = provider.state ? await provider.state() : undefined; + // Start new authorization flow const { authorizationUrl, codeVerifier } = await startAuthorization(serverUrl, { metadata, clientInformation, + state, redirectUrl: provider.redirectUrl, scope: scope || provider.clientMetadata.scope, }); @@ -211,11 +219,13 @@ export async function startAuthorization( clientInformation, redirectUrl, scope, + state, }: { metadata?: OAuthMetadata; clientInformation: OAuthClientInformation; redirectUrl: string | URL; scope?: string; + state?: string; }, ): Promise<{ authorizationUrl: URL; codeVerifier: string }> { const responseType = "code"; @@ -256,6 +266,10 @@ export async function startAuthorization( codeChallengeMethod, ); authorizationUrl.searchParams.set("redirect_uri", String(redirectUrl)); + + if (state) { + authorizationUrl.searchParams.set("state", state); + } if (scope) { authorizationUrl.searchParams.set("scope", scope);