Skip to content

Commit 1ac4ddd

Browse files
committed
Update README: document auto-registration, test helpers, active_for_authentication guard, redirect_uri, custom views
- Remove outdated devise.rb manual setup step (engine handles it) - Add 'What the engine does automatically' section - Add redirect_uri to config example and option table - Document active_for_authentication? guard in sign-in flow - Add 'Custom login view' section (host views take precedence) - Add 'Testing' section with test helpers, CI job pattern - Reduce setup checklist from 4 items to 3
1 parent 4730250 commit 1ac4ddd

1 file changed

Lines changed: 93 additions & 9 deletions

File tree

README.md

Lines changed: 93 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ bin/rails db:migrate
2323

2424
## Host-app setup checklist
2525

26-
The generator creates the initializer and migration, but it cannot edit your `active_admin.rb` or `admin_user.rb`. Four things have to be in place:
26+
The generator creates the initializer and migration, but it cannot edit your `active_admin.rb` or `admin_user.rb`. Three things have to be in place:
2727

2828
### 1. `config/initializers/active_admin.rb`
2929

@@ -46,17 +46,19 @@ class AdminUser < ApplicationRecord
4646
end
4747
```
4848

49-
### 3. `config/initializers/devise.rb`
49+
### 3. `config/initializers/activeadmin_oidc.rb` (generated)
5050

51-
ActiveAdmin mounts Devise under `/admin`, so OmniAuth's path prefix has to match:
51+
Fill in at minimum `issuer`, `client_id`, and an `on_login` hook. Full reference below.
5252

53-
```ruby
54-
config.omniauth_path_prefix = "/admin/auth"
55-
```
53+
## What the engine does automatically
5654

57-
### 4. `config/initializers/activeadmin_oidc.rb` (generated)
55+
The gem's Rails engine handles several things so host apps don't have to:
5856

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`.
6062

6163
## Configuration
6264

@@ -70,6 +72,11 @@ ActiveAdmin::Oidc.configure do |c|
7072
# --- OIDC scopes ------------------------------------------------------
7173
# c.scope = "openid email profile"
7274

75+
# --- Redirect URI -----------------------------------------------------
76+
# Normally auto-derived from the callback route. Set explicitly when
77+
# behind a reverse proxy, CDN, or when the IdP requires exact matching.
78+
# c.redirect_uri = "https://admin.example.com/admin/auth/oidc/callback"
79+
7380
# --- Identity lookup --------------------------------------------------
7481
# Which AdminUser column to match existing rows against, and which
7582
# claim on the id_token/userinfo to read for the lookup.
@@ -106,6 +113,7 @@ end
106113
| `client_secret` | `nil` | Blank ⇒ PKCE public client |
107114
| `scope` | `"openid email profile"` | Space-separated OIDC scopes |
108115
| `pkce` | auto | `true` when `client_secret` is blank; overridable |
116+
| `redirect_uri` | `nil` (auto) | Explicit callback URL; needed behind reverse proxies |
109117
| `identity_attribute` | `:email` | AdminUser column used for lookup/adoption |
110118
| `identity_claim` | `:email` | Claim key read from the id_token/userinfo |
111119
| `admin_user_class` | `"AdminUser"` | String or Class for the host's admin user model |
@@ -218,8 +226,84 @@ AdminUser.last.oidc_raw_info
218226
* A login button is added to the ActiveAdmin sessions page via a prepended view override — no templates to edit.
219227
* 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.
220228
* After a successful callback the user is signed in and redirected to `/admin` (not the host app's `/`, which may not exist).
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.
221230
* 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.
222231

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:
235+
236+
```
237+
app/views/active_admin/devise/sessions/new.html.erb
238+
```
239+
240+
The engine detects the host-app file and does not prepend its own view, so yours takes precedence with no extra configuration.
241+
242+
## Testing
243+
244+
The gem ships test helpers for host apps that need to exercise OIDC sign-in flows in their own specs.
245+
246+
### Quick setup
247+
248+
```ruby
249+
# spec/rails_helper.rb (or spec/support/oidc.rb)
250+
require "activeadmin/oidc/test_helpers"
251+
```
252+
253+
This single `require` auto-configures RSpec:
254+
255+
* Includes `ActiveAdmin::Oidc::TestHelpers` in specs tagged `oidc_mode: true`
256+
* Resets OmniAuth mocks after each `oidc_mode` example
257+
* Skips `oidc_mode` specs when the AdminUser model doesn't have `:omniauthable` loaded (i.e. when OIDC is not the active auth backend)
258+
* When `CI_RUN_OIDC=true` is set, runs **only** `oidc_mode` specs (useful for a dedicated CI job)
259+
260+
### Available helpers
261+
262+
```ruby
263+
RSpec.describe "OIDC sign-in", type: :feature, oidc_mode: true do
264+
it "provisions a new user" do
265+
# Stubs OmniAuth to return a successful OIDC auth hash.
266+
# Merges given claims with sensible defaults (preferred_username, email, roles).
267+
stub_oidc_sign_in(sub: "alice-sub", claims: { "email" => "alice@example.com" })
268+
269+
visit new_admin_user_session_path
270+
click_button "Sign in with SSO"
271+
272+
expect(page).to have_current_path("/admin")
273+
end
274+
275+
it "handles IdP errors" do
276+
# Stubs OmniAuth to simulate a strategy failure.
277+
stub_oidc_failure(:invalid_credentials)
278+
279+
visit new_admin_user_session_path
280+
click_button "Sign in with SSO"
281+
282+
expect(page).to have_content("no permission")
283+
end
284+
end
285+
```
286+
287+
| Helper | Purpose |
288+
|---|---|
289+
| `stub_oidc_sign_in(sub:, claims: {})` | Mock a successful OIDC callback with the given `sub` and claims |
290+
| `stub_oidc_failure(message_key)` | Mock an OmniAuth strategy failure (e.g. `:invalid_credentials`) |
291+
| `reset_oidc_stubs` | Clear mocks and disable test mode (called automatically in `after`) |
292+
293+
### CI job pattern
294+
295+
For apps that support multiple auth backends (DB, LDAP, OIDC) selected at boot time, run OIDC specs in a separate CI job:
296+
297+
```yaml
298+
oidc-specs:
299+
steps:
300+
- run: cp config/oidc.yml.distr config/oidc.yml
301+
- run: bundle exec rails db:create db:migrate
302+
- run: CI_RUN_OIDC=true bundle exec rspec --tag oidc_mode
303+
```
304+
305+
The default job should exclude them: `bundle exec rspec --tag ~oidc_mode`.
306+
223307
## Security notes
224308

225309
### Choice of `identity_attribute`
@@ -238,7 +322,7 @@ The gem also adds a unique `(provider, uid)` partial index in its own install mi
238322

239323
### What's filtered from logs
240324

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.
242326

243327
## Logger
244328

0 commit comments

Comments
 (0)