|
1 | 1 | # activeadmin-oidc |
2 | 2 |
|
3 | | -> Status: pre-alpha — test plan only. No implementation yet. |
4 | | -
|
5 | 3 | OpenID Connect single sign-on for [ActiveAdmin](https://activeadmin.info/), with a first-class [Zitadel](https://zitadel.com/) preset. |
6 | 4 |
|
7 | 5 | This gem plugs generic OIDC SSO into ActiveAdmin's existing Devise stack. It builds on [`omniauth_openid_connect`](https://github.com/omniauth/omniauth_openid_connect) for the OIDC protocol and adds the wiring ActiveAdmin apps actually need: JIT user provisioning, role mapping from provider claims, a Zitadel preset for the nested `urn:zitadel:iam:org:project:roles` claim, a login-view override, and a single install generator. |
8 | 6 |
|
9 | 7 | ## Why not just follow the ActiveAdmin wiki? |
10 | 8 |
|
11 | | -The [ActiveAdmin OAuth wiki recipe](https://github.com/activeadmin/activeadmin/wiki/Log-in-through-OAuth-providers) is a 200-line copy-paste that only covers Google and doesn't handle role mapping, account linking, or Zitadel's claim shape. This gem packages the wiring once so you can `rails g activeadmin_oidc:install` and be done. |
| 9 | +The [ActiveAdmin OAuth wiki recipe](https://github.com/activeadmin/activeadmin/wiki/Log-in-through-OAuth-providers) is a 200-line copy-paste that only covers Google and doesn't handle role mapping, account linking, or Zitadel's claim shape. This gem packages the wiring once so you can `rails g active_admin:oidc:install` and be done. |
12 | 10 |
|
13 | 11 | ## What this gem does NOT reimplement |
14 | 12 |
|
15 | 13 | The OIDC protocol layer — discovery, JWKS, token verification, PKCE, nonce, state — is delegated to the maintained upstream [`omniauth_openid_connect`](https://github.com/omniauth/omniauth_openid_connect) gem. This gem is a convention-over-configuration wrapper, not a new OIDC client. |
16 | 14 |
|
| 15 | +## Installation |
| 16 | + |
| 17 | +```ruby |
| 18 | +# Gemfile |
| 19 | +gem "activeadmin-oidc" |
| 20 | +``` |
| 21 | + |
| 22 | +```sh |
| 23 | +bundle install |
| 24 | +bin/rails generate active_admin:oidc:install |
| 25 | +bin/rails db:migrate |
| 26 | +``` |
| 27 | + |
| 28 | +Then fill in `config/initializers/activeadmin_oidc.rb` with your issuer and client id. |
| 29 | + |
| 30 | +## Host-app setup checklist |
| 31 | + |
| 32 | +The generator can't modify your `active_admin.rb` or `admin_user.rb` for you. Make sure these are in place — the install generator will print a "next steps" reminder, but you may as well do them up front: |
| 33 | + |
| 34 | +1. **`config/initializers/active_admin.rb`** — uncomment both of these: |
| 35 | + |
| 36 | + ```ruby |
| 37 | + config.authentication_method = :authenticate_admin_user! |
| 38 | + config.current_user_method = :current_admin_user |
| 39 | + ``` |
| 40 | + |
| 41 | + Without these, `/admin` is public to anyone and the utility navigation (including the logout button) renders empty. |
| 42 | + |
| 43 | +2. **`app/models/admin_user.rb`** — the Devise call must include `:omniauthable` and declare the `oidc` provider: |
| 44 | + |
| 45 | + ```ruby |
| 46 | + class AdminUser < ApplicationRecord |
| 47 | + devise :database_authenticatable, |
| 48 | + :rememberable, |
| 49 | + :omniauthable, omniauth_providers: [:oidc] |
| 50 | + |
| 51 | + serialize :oidc_raw_info, coder: JSON |
| 52 | + end |
| 53 | + ``` |
| 54 | + |
| 55 | +3. **`config/initializers/devise.rb`** — ActiveAdmin mounts Devise under `/admin`, so OmniAuth's path prefix has to match: |
| 56 | + |
| 57 | + ```ruby |
| 58 | + config.omniauth_path_prefix = "/admin/auth" |
| 59 | + ``` |
| 60 | + |
| 61 | +4. **`config/initializers/activeadmin_oidc.rb`** (generated) — set at minimum `c.issuer`, `c.client_id`, and an `c.on_login` hook that decides whether a given user is allowed in. |
| 62 | + |
| 63 | +## Sign-in flow |
| 64 | + |
| 65 | +* A login button is added to the ActiveAdmin sessions page via a prepended view override — you don't need to edit any templates. |
| 66 | +* Clicking it POSTs to `/admin/auth/oidc` with a Rails CSRF token (the gem forces OmniAuth 2.x's authenticity check to delegate to Rails' forgery protection, so `button_to` just works). |
| 67 | +* After a successful callback the user is signed in and redirected directly to `/admin` — the gem doesn't assume the host has a `root` route. |
| 68 | +* Logout goes through Devise's stock session destroy; no `end_session_endpoint` ping to the IdP. If you want RP-initiated single-logout, override the destroy action in your host app. |
| 69 | + |
17 | 70 | ## Status and roadmap |
18 | 71 |
|
19 | 72 | Design and test plan are locked. See [`docs/TEST_PLAN.md`](docs/TEST_PLAN.md) for the full TDD/BDD roadmap. Implementation follows red-green-refactor in this order: |
|
0 commit comments