Replace OmniAuth redirect with Keycloak Direct Access Grants #157

Closed
opened 2026-06-07 04:33:44 +00:00 by ldraney · 1 comment
Owner

Type

Feature

Lineage

Corrects auth implementation from Phase 1 (#115). Original decision (2026-06-04) was Direct Access Grants with login form in the app. OmniAuth redirect flow was implemented instead by spike #132 / PR #134 without escalating the decision change. This restores the intended architecture.

Repo

ldraney/landscaping-assistant

User Story

As a landscaping app user (web or iOS)
I want to log in with a username and password on the app's own login page
So that I never see Keycloak's UI and the experience is consistent across web and turbo-ios

Context

The app currently uses OmniAuth OpenID Connect (Auth Code + PKCE) which redirects users to Keycloak's default grey login page. This breaks mobile UX expectations and doesn't match the turbo-ios App Store experience we're building toward. Direct Access Grants (ROPC) lets the app render its own login form and exchange credentials with Keycloak server-side. No redirect, no external UI.

Pre-requisite: Direct Access Grants must be enabled on the Keycloak client in pal-e-services Terraform (separate cross-repo task).

File Targets

Files to modify:

  • Gemfile — remove omniauth, omniauth_openid_connect, omniauth-rails_csrf_protection; add jwt if needed
  • config/initializers/omniauth.rb — delete entirely
  • app/controllers/sessions_controller.rb — rewrite create to POST username/password to Keycloak token endpoint, decode JWT, store in session
  • app/controllers/application_controller.rb — remove OmniAuth-specific checks (keycloak_configured? may need updating)
  • app/views/sessions/new.html.erb — replace Keycloak redirect button with username + password form
  • config/routes.rb — replace /auth/keycloak/* routes with post "/login"
  • spec/requests/sessions_spec.rb — rewrite for direct grants flow
  • spec/requests/role_access_spec.rb — update auth helpers
  • docs/app-architecture.md — update auth flow diagram and description
  • docs/keycloak-setup.md — note decision reversal, update ROPC section

Files NOT to touch:

  • app/controllers/application_controller.rb role-checking logic (require_role, current_user_roles) — these work off session data and don't care how the session was created

Acceptance Criteria

  • Login page shows username + password fields (no Keycloak redirect button)
  • POST /login with valid credentials authenticates and redirects to app
  • POST /login with bad credentials shows error on login page
  • All 5 test users can log in (lucas-super-admin, lucas-admin, lucas-lead, lucas-crew, lucas-client)
  • Super admin sees admin tabs (Today, Week, Properties, Crew, Person + Platform)
  • Logout clears session and terminates Keycloak session
  • Works identically in browser and turbo-ios WebView
  • No OmniAuth gems or middleware remain
  • All specs pass

Test Expectations

  • Request spec: POST /login with valid credentials creates session with correct roles
  • Request spec: POST /login with invalid credentials returns to login with error
  • Request spec: DELETE /logout clears session
  • Request spec: unauthenticated access redirects to /login
  • Existing role_access specs pass with updated auth helpers
  • Run command: bundle exec rspec

Constraints

  • Keycloak token endpoint: {KEYCLOAK_URL}/realms/{KEYCLOAK_REALM}/protocol/openid-connect/token
  • Grant type: password with client_id, client_secret, username, password, scope=openid
  • Decode ID token JWT for realm_access.roles (Base64 decode, Keycloak tokens are trusted server-side)
  • Keep session structure compatible: session[:user] = {username:, email:, roles:}
  • CSRF protection on the login form (Rails default authenticity token)

Checklist

  • PR opened
  • Tests pass
  • No unrelated changes
  • landscaping-assistant project
  • ldraney/landscaping-assistant #115 — original Keycloak login implementation
  • ldraney/landscaping-assistant #132 — spike that recommended Auth Code over ROPC
  • ldraney/landscaping-assistant #154 — assume_ssl band-aid (superseded by this)
### Type Feature ### Lineage Corrects auth implementation from Phase 1 (#115). Original decision (2026-06-04) was Direct Access Grants with login form in the app. OmniAuth redirect flow was implemented instead by spike #132 / PR #134 without escalating the decision change. This restores the intended architecture. ### Repo `ldraney/landscaping-assistant` ### User Story As a landscaping app user (web or iOS) I want to log in with a username and password on the app's own login page So that I never see Keycloak's UI and the experience is consistent across web and turbo-ios ### Context The app currently uses OmniAuth OpenID Connect (Auth Code + PKCE) which redirects users to Keycloak's default grey login page. This breaks mobile UX expectations and doesn't match the turbo-ios App Store experience we're building toward. Direct Access Grants (ROPC) lets the app render its own login form and exchange credentials with Keycloak server-side. No redirect, no external UI. Pre-requisite: Direct Access Grants must be enabled on the Keycloak client in pal-e-services Terraform (separate cross-repo task). ### File Targets Files to modify: - `Gemfile` — remove `omniauth`, `omniauth_openid_connect`, `omniauth-rails_csrf_protection`; add `jwt` if needed - `config/initializers/omniauth.rb` — delete entirely - `app/controllers/sessions_controller.rb` — rewrite `create` to POST username/password to Keycloak token endpoint, decode JWT, store in session - `app/controllers/application_controller.rb` — remove OmniAuth-specific checks (keycloak_configured? may need updating) - `app/views/sessions/new.html.erb` — replace Keycloak redirect button with username + password form - `config/routes.rb` — replace `/auth/keycloak/*` routes with `post "/login"` - `spec/requests/sessions_spec.rb` — rewrite for direct grants flow - `spec/requests/role_access_spec.rb` — update auth helpers - `docs/app-architecture.md` — update auth flow diagram and description - `docs/keycloak-setup.md` — note decision reversal, update ROPC section Files NOT to touch: - `app/controllers/application_controller.rb` role-checking logic (require_role, current_user_roles) — these work off session data and don't care how the session was created ### Acceptance Criteria - [ ] Login page shows username + password fields (no Keycloak redirect button) - [ ] POST /login with valid credentials authenticates and redirects to app - [ ] POST /login with bad credentials shows error on login page - [ ] All 5 test users can log in (lucas-super-admin, lucas-admin, lucas-lead, lucas-crew, lucas-client) - [ ] Super admin sees admin tabs (Today, Week, Properties, Crew, Person + Platform) - [ ] Logout clears session and terminates Keycloak session - [ ] Works identically in browser and turbo-ios WebView - [ ] No OmniAuth gems or middleware remain - [ ] All specs pass ### Test Expectations - [ ] Request spec: POST /login with valid credentials creates session with correct roles - [ ] Request spec: POST /login with invalid credentials returns to login with error - [ ] Request spec: DELETE /logout clears session - [ ] Request spec: unauthenticated access redirects to /login - [ ] Existing role_access specs pass with updated auth helpers - Run command: `bundle exec rspec` ### Constraints - Keycloak token endpoint: `{KEYCLOAK_URL}/realms/{KEYCLOAK_REALM}/protocol/openid-connect/token` - Grant type: `password` with `client_id`, `client_secret`, `username`, `password`, `scope=openid` - Decode ID token JWT for realm_access.roles (Base64 decode, Keycloak tokens are trusted server-side) - Keep session structure compatible: `session[:user] = {username:, email:, roles:}` - CSRF protection on the login form (Rails default authenticity token) ### Checklist - [ ] PR opened - [ ] Tests pass - [ ] No unrelated changes ### Related - `landscaping-assistant` project - `ldraney/landscaping-assistant #115` — original Keycloak login implementation - `ldraney/landscaping-assistant #132` — spike that recommended Auth Code over ROPC - `ldraney/landscaping-assistant #154` — assume_ssl band-aid (superseded by this)
Author
Owner

Reading this issue to plan the Direct Access Grants rewrite. This is the proper auth architecture per the 2026-06-04 decision -- OmniAuth redirect flow was implemented by mistake and is currently broken in prod.

Reading this issue to plan the Direct Access Grants rewrite. This is the proper auth architecture per the 2026-06-04 decision -- OmniAuth redirect flow was implemented by mistake and is currently broken in prod.
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
ldraney/landscaping-assistant#157
No description provided.