Skip to content

fix(connector-kit): reject none prompt combined with other prompts - #9566

Open
Shivam8584 wants to merge 2 commits into
logto-io:masterfrom
Shivam8584:fix/oidc-prompts-none-exclusive
Open

fix(connector-kit): reject none prompt combined with other prompts#9566
Shivam8584 wants to merge 2 commits into
logto-io:masterfrom
Shivam8584:fix/oidc-prompts-none-exclusive

Conversation

@Shivam8584

Copy link
Copy Markdown

Summary

Closes #9565.

oidcPromptsGuard accepted the OIDC none prompt alongside other values. Per OIDC Core 1.0 §3.1.2.1, none requests that no authentication or consent UI be shown, and a request containing none with any other value is an error.

Since the combination passed validation it could be saved from the Admin Console, and the connector then joined the values into a single prompt parameter. The identity provider was the first thing in the chain to reject it, so the end user landed on a provider error page instead of a sign-in screen:

Error 400: invalid_request
Invalid parameter value for prompt: Invalid prompt: select_account consent none

The guard is shared by the Google and Azure AD connectors, so fixing it in connector-kit covers both.

Change

.refine(
  (prompts) => !prompts.includes(OidcPrompt.None) || prompts.length === 1,
  { message: 'The `none` prompt cannot be combined with other prompts.' }
)

The connector already encodes one provider rule next to this — it filters login out of Google's select items because Google does not support it — so invalid values were handled; only the mutually-exclusive combination was missed.

Tests

New packages/toolkit/connector-kit/src/types/social.test.ts covers:

  • each prompt on its own is accepted
  • combinations without none are accepted (select_account consent, and with login)
  • none with any other prompt is rejected, in three orderings
  • an empty array and undefined are still accepted
  • values outside the enum are still rejected

I confirmed the test fails against the guard as it stands on master — parsing ["select_account","consent","none"] returns success: true before this change and false after, so the test genuinely covers the regression.

Verified against zod@3.24.3 as pinned in the workspace root.

Notes

  • Adds a changeset for @logto/connector-kit (patch).
  • No migration needed. An existing tenant that already stored the invalid combination keeps working exactly as before — the request was already being rejected by the provider — but the config can no longer be re-saved in that state, which is what surfaces the problem to the operator instead of to the end user.

OIDC Core 1.0 section 3.1.2.1 says a request containing `none` with any
other prompt value is an error, since `none` asks for no UI at all.

`oidcPromptsGuard` validated each element against the enum but never the
combination, so the Admin Console accepted it and the connector joined the
values verbatim into one `prompt` parameter. The identity provider was the
first thing to reject it: Google returns `invalid_request: Invalid prompt:
select_account consent none`, so the user lands on a provider error page
instead of a sign-in screen.

The guard is shared by the Google and Azure AD connectors, so fixing it in
connector-kit covers both.
Copilot AI lite review requested due to automatic review settings September 8, 2026 16:37
@github-actions

github-actions Bot commented Sep 8, 2026

Copy link
Copy Markdown

COMPARE TO master

Total Size Diff 📈 +3.02 KB

Diff by File
Name Diff
.changeset/rude-donuts-brake.md 📈 +849 Bytes
packages/toolkit/connector-kit/src/types/social.test.ts 📈 +1.57 KB
packages/toolkit/connector-kit/src/types/social.ts 📈 +636 Bytes

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

The new test calls oidcPromptsGuard.safeParse() with no argument, which will fail TypeScript type-checking and should be changed to safeParse(undefined).

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

This PR fixes OIDC prompt validation in @logto/connector-kit so the mutually-exclusive none prompt is rejected when combined with other prompt values, aligning behavior with OIDC Core 1.0 §3.1.2.1 and preventing provider-side invalid_request errors from surfacing to end users.

Changes:

  • Add a Zod .refine() to oidcPromptsGuard to reject none when other prompts are present.
  • Add unit tests covering valid prompt combinations and the invalid none+other cases.
  • Add a patch changeset for @logto/connector-kit.
File summaries
File Description
packages/toolkit/connector-kit/src/types/social.ts Tightens OIDC prompt validation by rejecting none when combined with other prompts.
packages/toolkit/connector-kit/src/types/social.test.ts Adds Vitest coverage for the updated prompt guard, including invalid combinations.
.changeset/rude-donuts-brake.md Adds a patch changeset describing the validation fix and its user impact.
Review details
  • Files reviewed: 3/3 changed files
  • Comments generated: 1
  • Review effort level: Lite

💡 Add a code-review agent skill for context-aware, tailored reviews. Learn more in the docs.


it('still accepts an empty array and undefined', () => {
expect(oidcPromptsGuard.safeParse([]).success).toBe(true);
expect(oidcPromptsGuard.safeParse().success).toBe(true);
`safeParse` declares its data parameter as required, so calling it with no
argument is a type error. Keep the explicit `undefined` and disable
`unicorn/no-useless-undefined` on that line, matching how the rule is handled
in packages/core/src/utils/zod.test.ts.
Copilot AI review requested due to automatic review settings September 8, 2026 18:41
@Shivam8584

Copy link
Copy Markdown
Author

Good catch, thanks — fixed in d22d31d.

safeParse declares its data parameter as required, so the no-argument call was error TS2554: Expected 1-2 arguments, but got 0 (confirmed against zod@3.24.3 as pinned here). It came from an eslint --fix run: unicorn/no-useless-undefined rewrites safeParse(undefined) to safeParse(), which then fails type-checking.

I've restored the explicit undefined and disabled that rule on the line, matching how the same conflict is handled in packages/core/src/utils/zod.test.ts. ESLint and tsc are both clean on the changed files now, and the 5 tests still pass.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟢 Approval recommended

The change is narrowly scoped, aligns with the OIDC spec requirement, and is covered by targeted unit tests plus an appropriate changeset.

Review details
  • Files reviewed: 3/3 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

.nativeEnum(OidcPrompt)
.array()
.refine((prompts) => !prompts.includes(OidcPrompt.None) || prompts.length === 1, {
message: 'The `none` prompt cannot be combined with other prompts.',

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The validation rule makes sense, but this guard is also used at runtime, so the impact is broader than rejecting config saves.

In the Google connector, getUserInfo and getTokenResponseAndUserInfo validate the full config before processing One Tap credentials. getAccessTokenByRefreshToken also validates the full config before making the token request. Neither flow sends the configured prompt parameter.

An existing config containing none alongside another prompt could therefore still work for One Tap or refresh-token operations today, but those operations would start failing with InvalidConfig after this change. This contradicts the PR note that existing invalid configs “keep working exactly as before.”

Could we clarify how existing configs should be handled and add regression coverage for these paths? If preserving those working flows is intended, we should separate save-time prompt validation from runtime validation for operations that do not use prompts. If rejecting the entire config at runtime is intentional, that behavior change should be explicitly acknowledged in the PR and release notes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Development

Successfully merging this pull request may close these issues.

bug: oidcPromptsGuard allows none combined with other prompts, breaking Google sign-in

3 participants