You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The gem's Rails engine handles several things so host apps don't have to:
58
56
59
-
Fill in at minimum `issuer`, `client_id`, and an `on_login` hook. Full reference below.
57
+
***OmniAuth strategy registration** — the engine registers the `:openid_connect` strategy with Devise automatically based on your `ActiveAdmin::Oidc` configuration. You do **not** need to add `config.omniauth` or `config.omniauth_path_prefix` to `devise.rb`.
58
+
***Callback controller** — the engine patches `ActiveAdmin::Devise.controllers` to route OmniAuth callbacks to the gem's controller. No manual `controllers: { omniauth_callbacks: ... }` needed in `routes.rb`.
59
+
***Login view override** — the engine prepends an SSO-only login page (no email/password fields) to the sessions controller's view path. If your host app ships its own `app/views/active_admin/devise/sessions/new.html.erb`, the gem detects it and backs off — your view wins.
60
+
***Path prefix** — the engine sets `Devise.omniauth_path_prefix` and `OmniAuth.config.path_prefix` to `/admin/auth` so the middleware intercepts requests under ActiveAdmin's mount point. Compatible with Rails 7.2+ and Rails 8's lazy route loading.
61
+
***Parameter filtering** — `code`, `id_token`, `access_token`, `refresh_token`, `state`, and `nonce` are added to `Rails.application.config.filter_parameters`.
60
62
61
63
## Configuration
62
64
@@ -70,6 +72,11 @@ ActiveAdmin::Oidc.configure do |c|
|`identity_attribute`|`:email`| AdminUser column used for lookup/adoption |
110
118
|`identity_claim`|`:email`| Claim key read from the id_token/userinfo |
111
119
|`admin_user_class`|`"AdminUser"`| String or Class for the host's admin user model |
112
120
|`login_button_label`|`"Sign in with SSO"`| Label on the login-page button |
113
121
|`access_denied_message`| generic | Flash shown on any denial |
114
-
|`on_login`|— (required) | Authorization hook; see below |
122
+
|`on_login`|-- (required) | Authorization hook; see below |
115
123
116
124
## The `on_login` hook
117
125
118
-
`on_login` is the **only** place authorization lives. The gem handles authentication (the user proved who they are via the IdP); deciding whether that user is allowed into the admin panel — and what they can see once they are in — is the host application's problem. The gem does not ship a role model.
126
+
`on_login` is the **only** place authorization lives. The gem handles authentication (the user proved who they are via the IdP); deciding whether that user is allowed into the admin panel -- and what they can see once they are in -- is the host application's problem. The gem does not ship a role model.
# Exceptions raised inside the hook are logged at :error via
142
150
# ActiveAdmin::Oidc.logger and surface to the user as the same
143
-
# generic denial flash — the callback action never 500s.
151
+
# generic denial flash -- the callback action never 500s.
144
152
true
145
153
}
146
154
```
147
155
148
-
### Example A — Zitadel nested project roles claim
156
+
### Example A -- Zitadel nested project roles claim
149
157
150
158
Zitadel emits roles under the custom claim `urn:zitadel:iam:org:project:roles`, shaped as `{ "role-name" => { "org-id" => "org-name" } }`. Flatten the keys into a string array on the AdminUser.
Every key the IdP returns in the id_token or userinfo is passed to `on_login` as part of `claims`. Custom claims work the same as standard ones — just read them by key:
201
+
Every key the IdP returns in the id_token or userinfo is passed to `on_login` as part of `claims`. Custom claims work the same as standard ones -- just read them by key:
The full claim hash (minus `access_token` / `refresh_token` / `id_token`) is also stored on the admin user as `oidc_raw_info`— a JSON column created by the install generator. Read it later outside the hook for debugging or for showing the user's profile from the IdP:
217
+
The full claim hash (minus `access_token` / `refresh_token` / `id_token`) is also stored on the admin user as `oidc_raw_info`-- a JSON column created by the install generator. Read it later outside the hook for debugging or for showing the user's profile from the IdP:
* A login button is added to the ActiveAdmin sessions page via a prepended view override — no templates to edit.
226
+
* A login button is added to the ActiveAdmin sessions page via a prepended view override -- no templates to edit.
219
227
* Clicking it POSTs to `/admin/auth/oidc` with a Rails CSRF token. The gem loads `omniauth-rails_csrf_protection` so OmniAuth 2.x delegates its authenticity check to Rails' forgery protection and `button_to` just works.
220
228
* After a successful callback the user is signed in and redirected to `/admin` (not the host app's `/`, which may not exist).
221
-
* Logout goes through Devise's stock session destroy. No RP-initiated single-logout ping to the IdP — override the destroy action in your host app if you need that.
229
+
***Disabled/locked users are rejected.** Devise's `active_for_authentication?` is checked after provisioning but before sign-in. If your model overrides this method (e.g. to check an `enabled` flag or Devise's `:lockable` module), the guard fires on OIDC sign-in too -- the user sees an appropriate flash and is redirected to the login page.
230
+
* Logout goes through Devise's stock session destroy. No RP-initiated single-logout ping to the IdP -- override the destroy action in your host app if you need that.
231
+
232
+
## Custom login view
233
+
234
+
The gem ships a minimal SSO-only login page (a single button, no email/password fields). If you need a different layout -- for instance, a combined SSO + password form for a break-glass mode -- drop your own template at:
The default job should exclude them: `bundle exec rspec --tag ~oidc_mode`.
222
306
223
307
## Security notes
224
308
225
309
### Choice of `identity_attribute`
226
310
227
-
The `identity_attribute` column is used to adopt existing AdminUser rows on first SSO login — an existing user with that value in that column, and no `provider`/`uid` yet, gets linked to the IdP identity. **Do not** point this at a column the IdP can influence and that is also security-sensitive. Safe choices: `:email`, `:username`, `:employee_id`. Unsafe choices: `:admin`, `:super_admin`, `:password_digest`, `:roles`— anything whose value encodes a permission.
311
+
The `identity_attribute` column is used to adopt existing AdminUser rows on first SSO login -- an existing user with that value in that column, and no `provider`/`uid` yet, gets linked to the IdP identity. **Do not** point this at a column the IdP can influence and that is also security-sensitive. Safe choices: `:email`, `:username`, `:employee_id`. Unsafe choices: `:admin`, `:super_admin`, `:password_digest`, `:roles` -- anything whose value encodes a permission.
228
312
229
313
### Unique index on the identity column
230
314
@@ -238,7 +322,7 @@ The gem also adds a unique `(provider, uid)` partial index in its own install mi
238
322
239
323
### What's filtered from logs
240
324
241
-
The initializer merges `code`, `id_token`, `access_token`, `refresh_token`, `state`, and `nonce` into `Rails.application.config.filter_parameters` so a mid-callback crash can't dump them into production logs. Your own `filter_parameters` entries are preserved.
325
+
The engine merges `code`, `id_token`, `access_token`, `refresh_token`, `state`, and `nonce` into `Rails.application.config.filter_parameters` so a mid-callback crash can't dump them into production logs. Your own `filter_parameters` entries are preserved.
0 commit comments