Configuring SSO Roles¶
How to map your IdP groups to CampusCore roles so that signed-in users automatically get the right access. Run through this once when standing up SSO for a new institution, then revisit when their group structure changes.
What you need from the institution¶
Before you start, get from the institution's IT contact:
- The exact name of the IdP claim that contains group/role membership. Common names:
groups,member_of,roles,urn:oid:1.3.6.1.4.1.5923.1.5.1.1. Microsoft Entra (Azure AD) typically usesgroups. Active Directory Federation Services often usesmember_ofwith LDAP DNs. - The exact group values they want to map to CampusCore roles. For LDAP-style providers this means full DNs like
CN=Advisors,OU=Staff,DC=vsu,DC=edu. For OIDC providers this is usually a short name likeadvisors. - Whether their group values are case-sensitive. Active Directory groups are case-insensitive; OIDC group names are usually case-sensitive.
Step 1 — Configure the SSO claim mapping¶
- Log into Django admin at
https://<deployment>/admin/as a CampusCore platform admin (superuser). - Open SSO Providers → click the institution's provider.
- Scroll to the Map your IdP claims to CampusCore fields section.
- Fill in the structured claim columns. Each value is the claim name on the IdP side:
| CampusCore field | What it's for | Example values |
|---|---|---|
| Email claim | Required. The user's email address. | email, mail, EmailAddress |
| External ID claim | Required. Stable subject ID. | sub, NameID, oid |
| First name claim | Optional. | given_name, firstName, givenName |
| Last name claim | Optional. | family_name, lastName, sn |
| Groups/roles claim | Where to look for the user's groups. Leave blank to disable role sync from this provider. | groups, member_of, roles |
| Department claim | Optional. | department, ou |
| Student ID claim | Optional. | employeeNumber, student_id |
-
If the institution uses any custom claims that aren't in the structured columns above, paste them as a JSON object into Extra attribute mapping:
These get stored on the user'sUserProfile.extra_attributesfield for later inspection. -
(Optional but recommended) Set Default role to whichever CampusCore role users should get when they sign in but don't match any group mapping. Most deployments use
studenthere. -
Save.
Step 2 — Create the SSO group mappings¶
- Still in the SSO Provider edit page, scroll to the inline SSO Group Mappings section.
- For each group → role pairing, add a row:
- IdP group value: The exact value the IdP returns. Paste it verbatim from what the institution gave you.
- Match mode: Pick
Exact matchfor case-sensitive providers (most OIDC). PickCase-insensitive matchfor Active Directory and other case-insensitive providers. - Role: The CampusCore role to grant.
-
Description: Optional note for the next person who looks at this mapping in 18 months.
-
One IdP group can map to multiple roles. Add a second row with the same
idp_group_valuebut a different role. Both will be granted. -
Multiple IdP groups can map to the same role. Useful when an institution has, e.g., both
advising_freshmanandadvising_seniorAD groups that should both get theadvisorrole. -
Save.
Step 3 — Verify with a test user¶
- Find a real user in one of the mapped groups and have them log in.
- In Django admin, open User Roles and search for their email.
- Confirm the expected role appears with
source=ssoandsource_sso_providerset to the institution's provider. - Open Audit Logs and look for the most recent
role_syncentry for that user — it should show the added/removed role IDs. - Have the user navigate to Settings → Roles & Permissions in the in-app UI. They should see the role catalog and your new mappings listed.
Common gotchas¶
- The user logs in but no roles get assigned. Most often the
Groups/roles claimis misspelled or the institution sends groups under a nestedattributeskey. Check the AuditLog forrole_sync_no_matchentries — that means CampusCore saw groups but none matched a mapping. - Active Directory DN strings get split into multiple groups. They shouldn't —
RoleSyncServicedetects=in the string and treats it as a single LDAP DN. If you see multiple roles being granted from one DN, file a bug. - A user's roles disappear after a known-good login. The IdP might be returning an empty
groupsarray. CampusCore deliberately does NOT wipe roles when the assertion's groups claim is empty (this would lock people out during transient IdP bugs). Check the IdP-side group membership. - You add a new mapping but a user doesn't get the new role. Roles only sync at login time. The user has to log out and log back in.
- A platform admin grants a role manually and the next SSO login wipes it. Manual grants are preserved across SSO syncs as long as they were created with
source='manual'(which the Django admin form does automatically). If you're scripting it, setsource='manual'explicitly.
Reference: how role sync runs under the hood¶
On every successful SSO login, CampusCore's user_logged_in signal receiver calls RoleSyncService.sync_user_roles_from_sso. The service:
- Reads
claim_groupsfrom the SSO provider config. - Extracts the group values from the assertion, normalizing across four shapes (list of strings, comma-separated string, Azure-AD-style list of dicts, LDAP DN string).
- Looks up matching
SSOGroupMappingrows. - Computes the diff against the user's existing
source='sso'UserRole rows. - Applies the diff under
select_for_updateso concurrent logins don't race. - Logs a
role_syncaudit entry containing the SSOGroupMapping IDs (never the raw group strings — avoids PII leak). - Cycles the session key on role change.
If anything fails, the failure is logged but the login is allowed to proceed. A broken role sync must never lock users out.
For full design details see docs/project/rbac-and-feature-flags.md.