# Configuring BookStack (group-aware pattern)

**Pattern:** group-aware. BookStack authenticates users through Keycloak and reads their group membership from the token, mapping Keycloak groups onto BookStack roles so that access is driven entirely by the IdP. This is the pattern for any app that should grant permissions based on what groups a user belongs to.

## Keycloak side

### The client

| Setting | Value | Why |
| --- | --- | --- |
| Client ID | `bookstack` | Matches `OIDC_CLIENT_ID` in the app. |
| Client authentication | On (confidential) | Back-channel code exchange with a client secret. |
| Standard flow | Enabled | Authorization-code flow. |
| PKCE method | S256 | Matches the app. |
| Valid redirect URI | `https://bookstack.example.org/oidc/callback` | App callback. |
| Valid post-logout redirect URI | `https://bookstack.example.org/` | Where the user lands after single logout. |

BookStack accepts **only RS256** for token signing. Keycloak signs with RS256 by default, so this usually needs no change, but it is worth confirming: if the realm or client is ever set to a different algorithm, BookStack rejects the tokens as invalid even though everything else is correct.

### The group scope and mapper

#### Step 1: Create the client scope
In your Keycloak realm, go to **Client scopes** and click **Create client scope**.

On the **Settings** tab:

| Field | Value | Why |
| --- | --- | --- |
| Name | `groups` | This is the name the application requests. It must match `OIDC_ADDITIONAL_SCOPES` in BookStack exactly. |
| Type | Default | Applied automatically to clients it is assigned to, without the client having to ask for it per request. |
| Include in token scope | On | Without this the scope is not honored when requested. |
| Include in OpenID Provider Metadata | On | Advertises the scope in the discovery document. |

Then go to the **Mappers** tab, click **Add mapper -> By configuration**, and select **Group Membership**:

| Field | Value | Why |
| --- | --- | --- |
| Name | `kc_groups` | A label for the mapper itself. |
| Token Claim Name | `kc_groups` | The key the group list arrives under in the token. This must match `OIDC_GROUPS_CLAIM` in BookStack. Note it differs from the scope name above. |
| Full group path | On | Sends `/bookstack/admin` rather than `admin`. See the note on group matching below. |
| Add to ID token | **On** | The setting that matters most. BookStack reads groups from the ID token; if this is off, everything else is correct and BookStack still sees nothing. |
| Add to access token | On | |
| Add to userinfo | On | |

#### Step 2: Assign the scope to the client
Go to **Clients**, click your `bookstack` client, and open the **Client scopes** tab.
1. Click **Add client scope** and select the `groups` scope you just created.
2. In the scope list, click the dropdown beside `groups` and choose **Default**.

Assigning it as *Default* rather than *Optional* means it is included on every authentication without the client needing to request it explicitly.

#### Step 3: Create the groups
Under **Groups**, create a parent group `bookstack` with children `admin`, `editor`, and `viewer`. With full group path enabled, membership arrives in the token as `/bookstack/admin` and so on. A user's membership in these groups is what drives their BookStack role.

## BookStack side

BookStack is configured through environment variables (here, in its `docker-compose.yml`). The OIDC-relevant ones, secrets redacted:

```yaml
environment:
  - AUTH_METHOD=oidc
  - AUTH_AUTO_INITIATE=false            # show the login page; don't force-redirect to Keycloak
  - OIDC_NAME=LandisFam SSO
  - OIDC_DISPLAY_NAME_CLAIMS=name
  - OIDC_CLIENT_ID=bookstack
  - OIDC_CLIENT_SECRET=REDACTED
  - OIDC_ISSUER=https://auth.example.org/realms/landisfam
  - OIDC_ISSUER_DISCOVER=true           # resolve endpoints from the discovery document
  - OIDC_ADDITIONAL_SCOPES=groups       # request the 'groups' scope (must exist in Keycloak)
  - OIDC_GROUPS_CLAIM=kc_groups            # read groups from the 'groups' claim
  - OIDC_USER_TO_GROUPS=true            # map incoming groups onto BookStack roles
  - OIDC_END_SESSION_ENDPOINT=true      # enable single logout
```

For group mapping to take effect, BookStack roles must exist whose names match the Keycloak group names it receives (`admin`, `editor`, `viewer`). BookStack matches the incoming group to a role of the same name.

> A note on `OIDC_REMOVE_FROM_GROUPS`: this option makes Keycloak the sole authority on group membership, removing a user from BookStack roles they are no longer in on the IdP. It is powerful and correct in principle, but enable it only *after* group mapping is confirmed working, or a misconfigured claim can strip everyone's access on their next login.

### Map bookstack roles to Keycloak groups
BookStack matches incoming groups against each role's External Authentication ID, not against the role name. In BookStack, go to Settings -> Roles, and edit each role mapping the External Authentication ID to the full Keycloak group path:
| Bookstack Role | External Authentication ID |
| --- | --- |
| Admin | `/bookstack/admin` |
| Editor | `/bookstack/editor` |
| Viewer | `/bookstack/viewer` |

## Adapt this for…

Any application that maps IdP groups to internal roles: wikis, dashboards, file platforms, ticketing systems. The shape is always: create a real client *scope* (not just a mapper) whose name matches what the app requests, put the group-membership mapper on it, and confirm the claim lands in the specific token the app reads.