Skip to content

feat: add Keycloak OIDC integration#3340

Draft
boehlke wants to merge 18 commits intoOpenSlides:mainfrom
kryptance:feature/keycloak-oidc
Draft

feat: add Keycloak OIDC integration#3340
boehlke wants to merge 18 commits intoOpenSlides:mainfrom
kryptance:feature/keycloak-oidc

Conversation

@boehlke
Copy link
Copy Markdown

@boehlke boehlke commented Feb 19, 2026

Summary

  • OIDC token validator with JWKS verification and Keycloak admin client factory
  • oidc-provision and who-am-i endpoints for OIDC session management
  • Redis session invalidation for OIDC backchannel logout
  • Sync user CRUD operations to Keycloak via Admin API
  • Migration 0101: migrate existing users to Keycloak with Argon2 password hashes
  • Migration 0102: add keycloak_id to user view
  • Comprehensive integration tests (backchannel logout, user migration, user sync)

Context

This is the largest PR in the Keycloak OIDC integration series. Structured as 3 commits:

  1. Core: HTTP endpoints, OIDC validator, Keycloak admin client, auth adapter, schema
  2. User sync + migrations: Keycloak sync mixin, user actions, migrations 0101/0102
  3. Tests: Integration tests for all Keycloak functionality

Related PRs:

  • openslides-auth-service: #918 (osauthlib with OIDC)
  • openslides-go: #170 (Go OIDC auth)
  • openslides-proxy: #35 (Traefik middleware)

🤖 Generated with Claude Code

boehlke and others added 15 commits March 9, 2026 10:20
- OIDC token validator with JWKS verification
- Keycloak admin client factory for Admin API operations
- oidc-provision and who-am-i endpoints
- Redis session invalidation for OIDC backchannel logout
- Auth adapter extensions for OIDC token validation
- PostgreSQL schema updates for keycloak_id column

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Sync user CRUD operations to Keycloak via Admin API (keycloak_sync_mixin)
- Save Keycloak account action for user provisioning
- Migration 0101: migrate existing users to Keycloak with Argon2 password hashes
- Migration 0102: add keycloak_id to user view

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Backchannel logout tests
- User migration tests (Argon2 hash import)
- User sync tests (CRUD operations to Keycloak)
- Update existing system tests for Keycloak-aware base classes

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Swap migration order so keycloak_id column (0101) is added before
the Keycloak user sync (0102) which depends on it.

Migration 0101 now only adds the column to user_t via ALTER TABLE.
The user view uses SELECT * and picks up new columns automatically,
so no view replacement is needed.

Also regenerates schema_relational.sql to include keycloak_id for
fresh database installs, and updates module references in
application.py and tests.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Apply Black, isort, pyupgrade formatting to all OIDC files
- Fix flake8 errors: remove unused imports (Optional, os), fix
  f-string without placeholders, fix E402 import ordering
- Fix mypy errors: add proper type annotations to test fixtures,
  fix implicit Optional parameters, update RouteFunction type alias
  to use RouteResponse, add type narrowing in base_view, fix
  test_presenter missing user_id argument
- All CI linting checks (black, isort, flake8, mypy, pyupgrade)
  now pass

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Point meta submodule to ec0bc10 (the actual HEAD of
feature/keycloak-oidc on kryptance/openslides-meta), replacing
the orphaned commit 761c67d that was force-pushed away.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add null checks for fetchone() results before indexing (3 sites)
- Change get_user() return type to dict[str, Any] | None
- Use column name instead of int index for DictRow in application.py

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…l.sql

The generated schema_relational.sql was missing the keycloak_id column
in user_t, which was added to models.yml but the SQL wasn't regenerated.
This caused the auth-service's PostgreSQL queries to fail with
"column keycloak_id does not exist".

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… sync

data_manipulation() from migration 0102 sets both migration 101 and 102
to FINALIZATION_REQUIRED. The previous code only reset migration 101,
leaving migration 102 stuck in FINALIZATION_REQUIRED state. This caused
"Missing 1 migrations to apply" errors on every backend API request
when OIDC mode was enabled with a fresh database.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Move user provisioning logic from BaseView._authenticate_oidc() to
oidc-provision route in ActionView. This ensures provisioning only happens
during the OIDC redirect flow, not on every authenticated request.

Changes:
- ActionView.oidc_provision_route(): Full provisioning flow with token
  validation, user info fetch, and user creation/update
- BaseView._authenticate_oidc(): Simplified to only authenticate and lookup
  existing users (removed provisioning logic)
- BaseView._provision_oidc_user(): Removed
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Import is_session_invalidated from base_view where it is defined,
not from oidc_validator where it does not exist.
In OIDC mode, the Traefik plugin manages the session. The provision
endpoint only needs to provision the user and redirect - no OpenSlides
auth token is needed.
Returns the remaining seconds until the access token expires,
allowing the client to proactively refresh before expiry.
@boehlke boehlke force-pushed the feature/keycloak-oidc branch 2 times, most recently from ed32294 to ec071dc Compare March 9, 2026 12:29
@boehlke boehlke force-pushed the feature/keycloak-oidc branch from ec071dc to 81db2fb Compare March 9, 2026 12:35
@boehlke boehlke changed the base branch from feature/relational-db to main March 11, 2026 14:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants