Skip to content

Update OAuth consent UI to handle OAuth 2.0 Auth Code + PKCE#13819

Open
rickyrombo wants to merge 1 commit intomjp-oauth-standalonefrom
mjp-oauth-consent
Open

Update OAuth consent UI to handle OAuth 2.0 Auth Code + PKCE#13819
rickyrombo wants to merge 1 commit intomjp-oauth-standalonefrom
mjp-oauth-consent

Conversation

@rickyrombo
Copy link
Contributor

No description provided.

@changeset-bot
Copy link

changeset-bot bot commented Mar 5, 2026

⚠️ No Changeset found

Latest commit: 844d0b0

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds support to the OAuth consent/login UI for OAuth 2.0 Authorization Code + PKCE by parsing PKCE params, validating them, exchanging the existing signed JWT for an authorization code, and redirecting/postMessaging the code back to the client.

Changes:

  • Add exchangeForAuthorizationCode() helper to POST JWT + PKCE params to /v1/oauth/authorize.
  • Parse response_type, code_challenge, code_challenge_method, and accept client_id as an alias for api_key.
  • Add PKCE-specific query param validation + user-facing error messages.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.

File Description
packages/web/src/pages/oauth-login-page/utils.ts Adds the JWT→auth-code exchange helper calling the backend OAuth authorize endpoint.
packages/web/src/pages/oauth-login-page/hooks.ts Parses PKCE params, validates them, and adds an auth-code redirect/postMessage path when response_type=code.
packages/web/src/pages/oauth-login-page/messages.ts Adds new PKCE validation error strings.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 115 to +134
} else if (scope === 'read') {
// Read scope-specific validations:
if (!appName && !apiKey) {
error = messages.missingAppNameError
}
} else if (scope === 'write') {
// Write scope-specific validations:
if (!apiKey) {
error = messages.missingApiKeyError
} else if (!isValidApiKey(apiKey)) {
error = messages.invalidApiKeyError
}
// PKCE-specific validations when response_type=code
if (!error && responseType === 'code') {
if (!codeChallenge || typeof codeChallenge !== 'string') {
error = messages.missingCodeChallengeError
} else if (codeChallengeMethod !== 'S256') {
error = messages.invalidCodeChallengeMethodError
}
}
Copy link

Copilot AI Mar 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PKCE query param validation is currently only applied inside the scope === 'write' branch. If response_type=code is used with scope=read (which is currently allowed when app_name is present) the UI will proceed without requiring client_id/apiKey, code_challenge, or a valid code_challenge_method, and later authorize() will attempt the code exchange with apiKey undefined. Consider moving the response_type=code validations outside the scope-specific branches (or explicitly rejecting response_type=code unless a client_id is present) so the required PKCE params are enforced consistently.

Copilot uses AI. Check for mistakes.
account,
userEmail,
apiKey: apiKey as string,
redirectUri: (redirectUri as string) ?? 'postMessage',
Copy link

Copilot AI Mar 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

redirectUri is defaulted to 'postMessage' here, but the rest of this module treats the special value as lowercase 'postmessage' (see parsing and redirect validation). This mismatch can cause the backend authorize call to receive an unexpected redirect_uri value if redirect_uri is ever missing/undefined. Prefer using 'postmessage' consistently (or better: derive the value from parsedRedirectUri and avoid a fallback that can bypass earlier validation).

Suggested change
redirectUri: (redirectUri as string) ?? 'postMessage',
redirectUri: (redirectUri as string) ?? 'postmessage',

Copilot uses AI. Check for mistakes.
Comment on lines +186 to +203
const res = await fetch(`${env.API_URL}/v1/oauth/authorize`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
token: jwt,
client_id: apiKey,
redirect_uri: redirectUri,
code_challenge: codeChallenge,
code_challenge_method: codeChallengeMethod,
scope
})
})
if (!res.ok) {
onError()
return null
}
const { code } = await res.json()
return code
Copy link

Copilot AI Mar 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fetch() can throw (network error, CORS failure), which will currently reject the promise and skip the !res.ok handling and onError() callback. Wrap the request + JSON parsing in a try/catch and call onError() on exceptions to keep error handling consistent with the rest of this file.

Suggested change
const res = await fetch(`${env.API_URL}/v1/oauth/authorize`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
token: jwt,
client_id: apiKey,
redirect_uri: redirectUri,
code_challenge: codeChallenge,
code_challenge_method: codeChallengeMethod,
scope
})
})
if (!res.ok) {
onError()
return null
}
const { code } = await res.json()
return code
try {
const res = await fetch(`${env.API_URL}/v1/oauth/authorize`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
token: jwt,
client_id: apiKey,
redirect_uri: redirectUri,
code_challenge: codeChallenge,
code_challenge_method: codeChallengeMethod,
scope
})
})
if (!res.ok) {
onError()
return null
}
const { code } = await res.json()
return code
} catch {
onError()
return null
}

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants