Skip to content

feat: add Keycloak auth adapter for Google SSO - #3

Open
ChakshuGautam wants to merge 440 commits into
developfrom
feat/keycloak-auth-adapter
Open

feat: add Keycloak auth adapter for Google SSO#3
ChakshuGautam wants to merge 440 commits into
developfrom
feat/keycloak-auth-adapter

Conversation

@ChakshuGautam

Copy link
Copy Markdown
Owner

Summary

  • Adds a pluggable auth adapter layer that supports both DIGIT's OTP-based auth and Keycloak SSO (Google, GitHub)
  • Activated by setting AUTH_PROVIDER=keycloak in globalConfigs.js — when not set, the existing DIGIT auth flow is used unchanged
  • Includes a unified login page with email/password + SSO provider buttons

Architecture

┌─────────────────────────────────┐
│  globalConfigs.js               │
│  AUTH_PROVIDER = "keycloak"     │
└──────────┬──────────────────────┘
           │
           ▼
┌──────────────────┐    ┌─────────────────────┐
│  AuthAdapter     │◄───│  DigitAuthAdapter    │  (default: OTP flow)
│  (interface)     │◄───│  KeycloakAuthAdapter │  (SSO: Google, GitHub)
└──────────────────┘    └─────────────────────┘
           │
           ▼
┌──────────────────────────────────────┐
│  Request.js (token proxy)            │
│  UserService (auth delegation)       │
│  PrivateRoute (route guards)         │
│  App.js (conditional login routing)  │
└──────────────────────────────────────┘

Files Changed

File What
auth/AuthAdapter.js Interface: login, signup, logout, SSO, isAuthenticated, getUser
auth/DigitAuthAdapter.js Wraps existing DIGIT auth (no behavior change)
auth/KeycloakAuthAdapter.js keycloak-js integration (CDN loaded), token exchange, user mapping
auth/index.js Factory: selects adapter based on AUTH_PROVIDER config
Request.js Routes API calls through token-exchange-svc when Keycloak token exists
User/index.js Delegates auth checks to active adapter
PrivateRoute.js Uses adapter.isAuthenticated() for route guards
core/App.js Conditional routing: UnifiedLogin (keycloak) vs LoginV2/SignUpV2 (DIGIT)
Login/index.js Unified login page with email + SSO buttons
public/index.html Adds keycloak-js CDN script
src/App.js Sets window.globalPath for route matching
src/index.js Initializes auth adapter before React render
webpack.config.js Resolve aliases for local core module + React singleton
docker/Dockerfile Allows lockfile updates for new deps

Key Design Decisions

  1. CDN for keycloak-js — Loaded via <script> tag rather than npm import to avoid bundling issues with the monorepo's complex dependency tree
  2. Token-only proxy — Pre-login API calls (MDMS init, localization) go direct to Kong; only authenticated calls route through token-exchange-svc
  3. No Fragment inside Switch — React Router v5's <Switch> only matches direct <Route> children; using <>...</> breaks route matching
  4. Hardcoded login labels — The login page renders before DIGIT's i18n system loads, so labels are hardcoded in English

Configuration Required

In globalConfigs.js:

var defined_auth_provider = "keycloak";
var defined_keycloak_url = "https://your-domain/auth";
var defined_keycloak_realm = "digit-sandbox";
var defined_keycloak_client_id = "digit-sandbox-ui";
var defined_token_exchange_url = "https://your-domain/token-exchange";

Test plan

  • Without AUTH_PROVIDER set: verify existing DIGIT OTP login works unchanged
  • With AUTH_PROVIDER=keycloak: verify Google SSO login redirects correctly
  • After SSO login: verify citizen pages load (complaints, city selection)
  • Verify API calls work (create complaint, search complaints) after SSO auth
  • Verify logout clears session and redirects to login

🤖 Generated with Claude Code

Lokendra-egov and others added 30 commits December 22, 2025 16:32
Removed the unnecessary tenant config copy from statea
…nce employee onboarding template generation

- Changed phase 2 description from "Common Master Data Upload" to "BOUNDARY MANAGEMENT WORKFLOW" in DataLoader.ipynb.
- Updated phase completion messages to reflect correct phase numbers in DataLoader.ipynb.
- Removed redundant validation layers section in 3_EmployeeOnboarding.ipynb.
- Enhanced dynamic employee template generation process in 3_EmployeeOnboarding.ipynb with clearer output and error handling.
- Improved UI layout and visibility toggles for tenant selection in 1_TenantAndCommonMaster.ipynb.
Chakshu and others added 28 commits April 3, 2026 06:50
Adds 12 E2E tests across 5 spec files with page objects:
- employee-login: form render, login+redirect, session storage, bad creds, API auth
- pgr-inbox: render, data/empty state, no raw SERVICEDEFS keys
- employee-logout: clears session, redirects
- citizen-flow: language page, continue navigation
- console-errors: no uncaught JS errors across employee flow

Also updates:
- nginx config: KC reverse proxy + inbox API route
- globalConfigs.js: KC auth provider settings
- CI workflow: Playwright install + run steps
- docker-compose: updated digit-ui image tag

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
These source files are required for the webpack build which resolves
@egovernments/digit-ui-libraries and @egovernments/digit-ui-react-components
via resolve.alias to local source paths. Without them, Docker builds
from a fresh clone fail with Module not found errors.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Two-part document covering the complete KC integration:
- Part 1: Technical spec (architecture, auth flows, components, multi-tenancy)
- Part 2: Gap analysis with 20 findings across security, scalability,
  reliability, operability, and correctness (3 critical, 7 high, 9 medium, 1 low)

All claims validated against source code (28 checks, 3 corrections applied).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
NestJS + Fastify + Bun rewrite resolving all 20 gaps:
- Module structure, gap-to-module mapping, bootstrap config
- 54 unit tests + 11 E2E tests covering every gap
- Dragonfly replaces Redis, docker-compose v2 with replicas
- KC realm config changes (audience mapper, disable ROPC for browser)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
13 tasks covering NestJS + Fastify + Bun rewrite:
- Project scaffolding + Zod config validation
- Cache module (Dragonfly + LRU fallback)
- Circuit breaker, Auth (JWT + audience), Metrics, Health modules
- DIGIT client (random passwords, mobile retry, user type)
- User resolver (realm-namespaced userName, staleness, role sync)
- Proxy + Login BFF controller
- KC admin with retry-backoff
- Docker + realm config + frontend adapter update
- Full test suite (54 unit + 12 E2E)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add esbuild.dev.js (live-reload dev server on port 18080)
- Switch esbuild to IIFE format (fixes ESM module ordering crash)
- Add process shim to index.html (fixes "process is not defined")
- Fix globalConfigs.js path in dev server
- Fix missing exports: ArrowRight, MdmsService
- Remove unused imports: start (date-fns), APPLY_FILTER
- Add react-responsive + esbuild deps to package.json
- Fix E2E logout test (sidebar navigation)
- Fix E2E inbox test (lenient assertions for async content)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace direct Keycloak ROPC token call with a JSON POST to the
token-exchange-svc BFF, which handles KC authentication and DIGIT
user enrichment server-side. The BFF does not return a refresh_token
so null is passed to _setTokens.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Non-critical 400 errors from DIGIT API (role sync, boundary lookups)
are expected and don't indicate JS bugs. Filter them like other
known benign errors.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
5 tests validating every frontend API route works through v2 proxy:
1. All employee flow APIs return valid responses (11 unique endpoints captured)
2. All API calls carry JWT — no 401s after login
3. Each critical API individually tested with JWT (MDMS, localization,
   access control, PGR, boundary, HRMS, workflow)
4. KC OIDC endpoints accessible (not blocked by proxy)
5. Citizen flow APIs work without authentication

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Catches all 4 SSO misconfigurations we hit in production:
- redirect_uri not in KC client
- deployment domain not in web origins
- KC frontendUrl pointing to wrong domain
- Google client secret mismatch

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Frontend calls /egov-user-event/v1/events/notifications/_count for
notification badges. This service was missing from Kong's route map,
causing 404. Added as user-event-service route.

Also added citizen-proxy test and Google SSO config tests.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The KC silent-check-sso iframe can hang indefinitely when:
- Third-party cookies are blocked (Safari, Firefox strict mode)
- KC session iframe CORS fails
- Network issues with the KC OIDC endpoints

Without a timeout, await initAuthAdapter() blocks forever and
ReactDOM.render() never runs — resulting in a blank white page.

Added 15-second Promise.race timeout. If SSO check doesn't complete
in time, the app renders without SSO state (user sees login page).

Also added debug logs throughout the citizen flow:
- bootstrap, initAuthAdapter, DigitAppWrapper, DigitApp routing,
  CitizenApp, EmployeeApp setType guard

Also fixed:
- DigitApp default redirect respects userType (citizen → /citizen)
- EmployeeApp skips setType for CITIZEN users
- DigitAppWrapper uses grounded-container for authenticated citizens

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…tizenHomeCard

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Fix UserService.logout not a function: explicit import from
  User/index.js instead of ambiguous User (esbuild picked User.js
  which has no logout method)
- Default citizen tenant to pg.citya (state-level pg has no boundaries)
- Add key props to LogoutDialog children/footer arrays

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Store id_token in localStorage during SSO login. On logout, pass it
as id_token_hint to KC's logout endpoint. KC skips the 'Do you want
to log out?' confirmation when the hint is present.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
KC adapter stores citizen info as Citizen.user-info (not user-info).
PGRInitialization couldn't find the citizen tenant, defaulted to
state-level pg (no boundaries), causing empty location dropdown.

Now checks both localStorage keys and falls back to Citizen.tenant-id.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
PGRInitialization was only running for employee users (line 50:
userType === 'employee'). Citizens need boundaryHierarchyOrder
in SessionStorage for the complaint location dropdown.

Without it, the dropdown renders empty because boundaryHierarchy=[]
in the BoundaryComponent.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Run usePGRInitialization for citizen users (was employee-only)
- Fallback state-level tenant (pg) to city-level (pg.citya) since
  boundaries are seeded at city level
- Read Citizen.user-info and Citizen.tenant-id for KC SSO users

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When keycloak-js init({onLoad:"check-sso"}) fails or times out (e.g.,
third-party cookies blocked, network issues), the app had no fallback
and showed the login page even though valid tokens were in localStorage.

Now the bootstrap reads Citizen.token/Citizen.user-info from localStorage
and populates SessionStorage, allowing the citizen PGR flow to work
without an active KC SSO session.

Also fixed BoundaryService.js to read Citizen.user-info and fallback
to city-level tenant (same fix as PGRInitialization).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Walks through the full complaint creation flow (type → map → landmark →
location) and validates that boundary APIs return data through the proxy.
Catches regressions in tenant ID rewriting for boundary-relationships
and hierarchy-definition endpoints.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…root dropdown

- Cloned @egovernments/digit-ui-components v0.2.3 source from DIGIT-UI-LIBRARIES
  repo (commit aa8b1b442b) into micro-ui-internals/packages/digit-ui-components/
- Fixed BoundaryFilter.js: root-level City dropdown used variant="nesteddropdown"
  which filters out flat options (no nested `options` arrays). Changed to
  variant="dropdown" for the root boundary type.
- Updated esbuild.dev.js to resolve digit-ui-components from local source for HMR
- Added stub exports for icons/hooks referenced by other packages but not in v0.2.3
- Updated ComplaintsLocation.js levelConfig defaults to match actual hierarchy
  (City/Locality instead of region/zone)
- Updated citizen-pgr-complaint E2E test to verify City dropdown has options
- Minor fixes: PGRInbox structuredClone, persister geoLocation defaults

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Remove digit-ui-components, react-components, and libraries source
from git tracking. These are upstream packages from egovernments/
DIGIT-UI-LIBRARIES and should be contributed back via separate PRs.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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.

9 participants