Skip to content

The scope editor pins openid, so an IdP that rejects it cannot be configured #522

Description

@snipereagle1

Tested against ghcr.io/quackbackio/quackback:main at digest sha256:b6edc570a72952b00c35d818901044f102965ca0d94069506a8960e40aa0d286, pulled 2026-09-08, carrying #373, #508, #509, #510 and #514.

Summary

Everything #337 asked for works. EVE Online now signs in end to end: identity resolves from the access-token JWT, a placeholder address is minted, and the admin "Test sign-in" passes and unlocks the enforcement gate.

One thing is left, and it is small: the admin editor renders openid as an unremovable scope, so a provider that rejects openid cannot be configured through the UI at all. I had to write the scope with SQL to complete the test. There is a workaround detailed below that allows me to use Quackback as it ships today.

The problem

EVE Online's authorize endpoint rejects openid outright:

{"error":"invalid_scope","error_description":"The requested 'openid' scope is not valid."}

openid is not one of its scopes. Its discovery document is served at https://login.eveonline.com/.well-known/openid-configuration and publishes scopes_supported: null, so nothing warns the admin in advance.

A new provider starts at DEFAULT_OIDC_SCOPES (oidc-scopes.ts:17), which is ['openid','email','profile']. In the editor, the openid token renders with the label required and no remove control:

{scope === REQUIRED_OIDC_SCOPE ? (
  <span className="text-[11px] text-muted-foreground">required</span>
) : (
  <button aria-label={`Remove scope ${scope}`} >×</button>
)}

advanced-section.tsx:122-127. supportedSubset (oidc-scopes.ts:74) also keeps openid regardless of what the IdP advertises, so the one-click "fix to supported scopes" path cannot remove it either.

The result is that no sequence of clicks reaches a working configuration for such a provider. Every authorize request carries openid and fails at the IdP before the user ever returns.

Where the restriction is and isn't

It is only in the editor. Both layers below accept any scope set:

  • sso.ts:228 validates the field as scopes: z.string().max(512).nullable().optional().
  • effectiveScopes (oidc-scopes.ts:39) returns the stored value verbatim whenever it parses to a non-empty set.

So the workaround is a direct write:

update identity_provider
   set scopes = 'publicData'
 where registration_id = '<registrationId>';

After that the editor prefills from the stored value and renders publicData alone, with a working × and no openid — the restriction only bites on the way out of the default set. I did not test whether pressing Save connection from that state round-trips the value intact.

Possible fix

Allowing openid to be removed seems sufficient. The comment at advanced-section.tsx:5-9 explains the current behaviour as protecting an admin from breaking OIDC by accident, which is fair — but a warning would serve that as well as a hard block, and would not exclude providers that are OAuth-shaped rather than OIDC-shaped.

REQUIRED_OIDC_SCOPE in supportedSubset would want the same treatment, otherwise "fix to supported scopes" silently reintroduces it.

There is a second, larger question you may or may not want to answer: effectiveScopes treats an empty or whitespace scope string as "use the defaults", so there is no way to express "send no scope parameter at all". EVE accepts that form — an authorization-only application with no scopes granted reaches character selection with no scope in the query string. That is a separate design decision from the unremovable openid, and only the first one blocked me, so I mention it only for completeness.

Verification that the rest works

With scopes = 'publicData' and this mapping:

{"profile":{"sources":["accessTokenJwt"],"claims":{"id":"sub","name":"name"},"allowMissingEmail":true}}

Portal sign-in completed and created:

user     name              snipereagle1
         email             sso-oidc-<id>-<hex>@anon.quackback.io
         email_verified    false

account  provider_id       oidc_<id>
         account_id        CHARACTER:EVE:1107376792
         has_id_token      false
         has_access_token  true

has_id_token = false alongside a resolved account_id is the interesting part: EVE returns no ID token at all, and identity came from the access-token JWT.

Test sign-in passed and stamped the gate — sso test succeeded; provider gates unlocked. The reported stages were state validation, discovery fetch, token exchange, account identifier resolved from accessTokenJwt, display name resolved from accessTokenJwt, no avatar released, and no email released with a placeholder to be created. No id-token-decode or signature-verify rows appeared, which is the hasIdToken guard at sso-test-handshake.ts:345 doing its job — that was limitation 1 in #337 and it is now gone.

The stored last_test_capture recorded the provenance directly:

"identity": {
  "id": "CHARACTER:EVE:1107376792",
  "name": "snipereagle1",
  "paths":   { "id": "sub", "name": "name" },
  "sources": { "id": "accessTokenJwt", "name": "accessTokenJwt" }
}

Reproducing without an EVE account

An EVE developer application needs an account with game time, so this may not be directly reproducible on your side. The behaviour does not depend on EVE, though — any OAuth provider that rejects openid, or any test double whose authorize endpoint 400s on it, reproduces the editor problem exactly. The configuration under test was:

discovery_url  https://login.eveonline.com/.well-known/openid-configuration
scopes         publicData
prompt         (default: login)
claim_mapping  {"profile":{"sources":["accessTokenJwt"],
                           "claims":{"id":"sub","name":"name"},
                           "allowMissingEmail":true}}

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions