Step 5 — Roles and SSO
The CRM works but every user is admin. Real applications have at least two profiles — operators who edit, viewers who read — and increasingly they sign in via the company's identity provider (Authentik, Keycloak, Azure AD, Okta) instead of a per-app password.
This step splits the CRM into three roles and adds OIDC sign-in. Estimated time: 15 minutes.
What we're doing and why
The framework's access-control model has two halves:
| Half | Where it lives |
|---|---|
| What can be done — atomic permission codes generated automatically per query, screen, dashboard, job. | The codes appear in the Permissions tab of every builder. We don't type them. |
| Who can do it — roles that group permissions, assigned to users. | Settings → Roles + Settings → Users. |
The split lets us define roles once and assign them to many users, without ever touching individual permission codes by hand.
For the CRM we'll set up three roles:
| Role | What they can do |
|---|---|
crm-viewer | See the dashboard and the screens, but no edit. |
crm-sales | Read + write everything on customers, deals, activities. |
crm-admin | Same as crm-sales + access to the Settings UI. |
Then we'll add an OIDC backend so users sign in with the company IdP.
Look at the permission codes you already have
Open Settings → Connectors → customers → Permissions tab. You see the four codes that the connector generated automatically:
| Code | Generated for |
|---|---|
sql:customers:list | The read query. |
sql:customers:create:write | The insert. |
sql:customers:update:write | The update. |
sql:customers:delete:write | The delete. |
Same for deals and activities. Plus a few that came from the dashboard and the menu:
| Code | Generated for |
|---|---|
screen:crm:customers | Visiting the Customers screen (page-level gate). |
screen:crm:deals | Same for Deals. |
dashboard:crm-pipeline-overview | The dashboard. |
menu:crm:* | The leaves of the crm menu. |
The framework gives us wildcards — sql:customers:* covers every customer query, screen:crm:* covers every CRM screen. We'll use wildcards for the role definitions.
Define crm-viewer
Settings → Roles → + New role:
| Field | Value |
|---|---|
| Id | crm-viewer |
| Display name | CRM viewer |
| Description | Read-only access to CRM screens and the pipeline dashboard. |
In the Permissions granted section, click + Add permission and add the codes that match read-only access:
| Code | What it grants |
|---|---|
sql:customers:list | Read customers. |
sql:deals:* (wildcard) | Read all deal queries — including the aggregates that feed the dashboard. We don't add the :write codes. |
sql:activities:list | Read activities. |
screen:crm:customers | Visit the Customers screen. |
screen:crm:deals | Visit the Deals screen. |
dashboard:crm-pipeline-overview | Visit the dashboard. |
menu:crm:* | See the CRM workspace + every leaf in it. The screens / dashboards themselves are gated; a leaf without permission for its target is hidden anyway. |
chart:crm-pipeline-by-stage | See the chart (otherwise the panel disappears). |
Save.
A read-only crm-viewer is now possible. Since they don't carry any :write codes, the framework renders every screen in read-only mode — Add / Edit / Delete buttons hidden, dialog opens in view mode.
Define crm-sales
+ New role again:
| Field | Value |
|---|---|
| Id | crm-sales |
| Display name | CRM sales |
| Description | Full read + write access to customers, deals and activities. |
| Inherits from | crm-viewer |
By inheriting from crm-viewer we get every read code for free; we just need to add the writes:
| Code | What it grants |
|---|---|
sql:customers:create:write | |
sql:customers:update:write | |
sql:customers:delete:write | |
sql:deals:create:write | |
sql:deals:update:write | |
sql:deals:delete:write | |
sql:activities:create:write | |
sql:activities:delete:write |
Save.
Define crm-admin
| Field | Value |
|---|---|
| Id | crm-admin |
| Display name | CRM admin |
| Inherits from | crm-sales |
| Code | What it grants |
|---|---|
settings:read | The Settings link appears in the header. |
settings:connectors | Edit connectors. |
settings:screens | Edit screens. |
settings:menus | Edit menus. |
settings:dashboards | Edit dashboards. |
settings:charts | Edit charts. |
settings:dictionary | Edit dictionary. |
settings:reload | The Save & reload button works. |
users:read + users:write | Manage users (we'll need this when OIDC creates new users). |
Note we deliberately don't give settings:framework or settings:raw — those are for the platform admin user only.
Save.
Create two users to test
Settings → Users → + New user:
| User | Roles |
|---|---|
sales-alice | crm-sales |
viewer-bob | crm-viewer |
Set a password for each, then sign out of admin and sign in as sales-alice. You should see:
- Workspace selector shows CRM (the only workspace Alice can see).
- Pipeline overview, Customers, Deals in the sidebar.
- Edit / Add / Delete buttons present.
- No Settings link in the header.
Sign in as viewer-bob:
- Same three sidebar entries (he can see them).
- Edit / Add / Delete buttons hidden — the screens render read-only.
- Clicking a row opens the dialog in view mode (no Save button).
- Trying to type
/settingsin the URL → 403.
The framework's pruning is silent: nothing says "you can't do this", the controls simply aren't there.
Wire OIDC sign-in
OIDC turns the company's identity provider into Liberty's authentication backend. Users sign in once, single sign-out, central audit. We'll set it up against Authentik as the example — the procedure is identical for Keycloak, Azure AD, Okta.
On Authentik
- Create an OAuth2/OpenID Provider in Authentik's admin.
- Set the Redirect URI to
http://127.0.0.1:8000/auth/oidc/callback(production: the real public URL). - Note the Client ID + Client secret that Authentik generates.
- Create an Application linked to the provider; pick the groups to surface in the token (we'll map them to Liberty roles).
On Liberty
Open Settings → Framework → Authentication and flip OIDC enabled on. Fill in:
| Field | Value |
|---|---|
| Issuer URL | https://auth.example.com/application/o/liberty/ (trailing slash matters in Authentik) |
| Client ID | the Authentik client id |
| Client secret | 🔒 the Authentik client secret (stored encrypted) |
| Redirect URI | http://127.0.0.1:8000/auth/oidc/callback |
| Email claim | email |
| Groups claim | groups |
| Auto-provision | ✓ (creates the Liberty user on first SSO sign-in) |
Click the ▶ Test sign-in button. A new browser tab opens, Authentik's login page appears, you authenticate, the tab returns "OIDC test successful" plus the resolved claims (email, groups) so you can confirm the mapping is right.
Save & reload.
Map IdP groups to Liberty roles
The framework maps the groups_claim 1:1 to Liberty role identifiers. So an Authentik group named crm-sales becomes the Liberty role crm-sales we already defined. Make sure the names match — that's the whole mapping configuration.
Sign out as admin. The login page now shows a Sign in with SSO button next to the username/password form. Click it, authenticate against Authentik, and you return to the framework with whatever roles the IdP group claim resolved to.
If Auto-provision is on (it is), users who SSO in for the first time are created on the spot with the right roles. No pre-registration needed.
Two things to know about OIDC
| Thing | Why it matters |
|---|---|
| The local backend stays available. | The username/password form still works for the admin user. Useful when the IdP is down — you can break-glass into Liberty without depending on Authentik being reachable. |
| The IdP groups are the source of truth. | If you remove a user from the crm-sales group in Authentik, their next sign-in lands them without crm-sales. The user record stays in Liberty (with no roles); the IdP decides who has access. |
What you have now
The CRM has real roles, real users, real SSO. Two operator profiles see different things; a manager who only browses gets a read-only experience by construction.
The last layer to add: AI access to the data and a nightly job that keeps the pipeline honest by flagging stale deals.