Warning

This document is for an old release of Galaxy. You can alternatively view this page in the latest release if it exists or view the top of the latest release's documentation.

OIDC authentication and account confirmation

Galaxy 25.1 has two authentication implementations. AuthnzManager routes Keycloak, CILogon, and Custos to OIDCAuthnzBase in custos_authnz.py; other providers use PSAAuthnz. Only the legacy OIDC implementation supports deferred account creation on this release. It stores associations in CustosAuthnzToken, using the provider’s subject as external_user_id. This backport does not migrate those associations or add a confirmation pipeline to python-social-auth.

Request flow

  1. Start authentication. OIDCAuthnzBase.authenticate creates the provider redirect and saves the nonce hash and optional PKCE verifier in FlowState. OAuth state identifies a record bound to the browser’s Galaxy session and the canonical provider name. These values no longer rely on separate cookies.

  2. Handle the callback. callback consumes authentication state before the code exchange. It restores the PKCE verifier and supplies the consumed state to OAuth2Session, which checks the callback URL’s state during the exchange. A failed exchange or identity check requires a new login attempt.

  3. Establish identity. _process_token reads the ID token returned directly by the configured token endpoint and checks the nonce, issuer, audience, expiry, subject, and authorized party when present. If it needs UserInfo for the email address, UserInfo’s subject must match the ID token’s subject. As in the legacy backend, trust in the token response relies on the direct exchange with the configured endpoint, rather than local signature verification. Production providers require HTTPS with certificate verification enabled. The callback never accepts a browser-supplied ID token.

  4. Defer creation. When a new account requires confirmation, the callback saves the established identity, provider tokens, and their original expiration times in a server-side confirmation record. It redirects to login/start?confirm=true&provider=...&confirmation_id=.... The URL contains no provider credentials. Existing-account login, explicit linking, and fixed_delegated_auth retain their legacy paths.

  5. Confirm or cancel. NewUserConfirmation.vue sends a form-encoded POST to /authnz/{provider}/create_user or /authnz/{provider}/cancel_user_creation, with confirmation_id and session_csrf_token in the body. Both actions enforce POST, CSRF protection, and anonymous registration context, including through generic controller routes. Legacy token and JSONP parameters are rejected. Confirmation consumes the record, rejects existing email or subject associations, and creates through UserManager, preserving activation policy. Cancellation consumes the record without creating an account.

  6. Refresh browser state. Successful confirmation logs in the user and returns JSON containing redirect_uri. The client performs a full navigation to reload user and CSRF state. Cancellation returns an empty JSON object and the client returns to login. Responses use Cache-Control: no-store.

State and failure boundaries

FlowState stores records in the existing PSAPartial table under a namespace and negative step numbers separate from resumable python-social-auth pipelines. No database migration is needed. Each record is bound to the Galaxy session, canonical provider, and purpose (authentication or confirmation). Records expire after at most ten minutes; confirmation expiry is also bounded by the ID token’s expiry. A new attempt replaces pending state for that browser/provider.

Consumption uses a conditional database delete, checks that the Galaxy session is still valid, and requires exactly one affected row. Do not replace this with an unconditional delete following a read: two workers could reuse the same record. Consumption commits before account-creation helpers, which can commit independently. A subsequent exchange, account-creation, or association failure must not make the record reusable. Email and subject checks reject associations created while the confirmation page was open; database uniqueness constraints also guard concurrent inserts.

Confirmation never decodes tokens or recomputes identity from client input. It preserves the callback’s subject and token expiration times. Token refresh, association persistence, and provider logout continue through the legacy backend. In-flight cookie-based logins and old confirmation links must be restarted after upgrading.

Regression coverage

test/integration/oidc/test_auth_oidc.py uses real Docker Keycloak and a Galaxy server. Its confirmation class enables PKCE and covers POST/CSRF, account creation, session refresh, cancellation, browser isolation, and callback replay. The other classes cover legacy login, account linking, logout, bearer tokens, and the independent PSA path. Run these using:

./run_tests.sh -integration test/integration/oidc/test_auth_oidc.py

test/unit/authnz/test_flow_state.py covers persistence, expiry, purpose and provider binding, invalidated sessions, and concurrent consumption. The PostgreSQL unit-test workflow also runs it against PostgreSQL. test_custos_confirmation.py covers forged browser credentials, identity validation, activation policy, collision checks, and failures after consumption. test_authnz_controller.py tests HTTP guards. The Jest Login component tests cover confirmation bodies, cancellation, errors, duplicate submission, legacy links, and full navigation after login, including applications under a URL prefix.