Skip to content

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:

  1. 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 uses groups. Active Directory Federation Services often uses member_of with LDAP DNs.
  2. 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 like advisors.
  3. 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

  1. Log into Django admin at https://<deployment>/admin/ as a CampusCore platform admin (superuser).
  2. Open SSO Providers → click the institution's provider.
  3. Scroll to the Map your IdP claims to CampusCore fields section.
  4. 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
  1. 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:

    {"workday_id": "employee_number"}
    
    These get stored on the user's UserProfile.extra_attributes field for later inspection.

  2. (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 student here.

  3. Save.

Step 2 — Create the SSO group mappings

  1. Still in the SSO Provider edit page, scroll to the inline SSO Group Mappings section.
  2. For each group → role pairing, add a row:
  3. IdP group value: The exact value the IdP returns. Paste it verbatim from what the institution gave you.
  4. Match mode: Pick Exact match for case-sensitive providers (most OIDC). Pick Case-insensitive match for Active Directory and other case-insensitive providers.
  5. Role: The CampusCore role to grant.
  6. Description: Optional note for the next person who looks at this mapping in 18 months.

  7. One IdP group can map to multiple roles. Add a second row with the same idp_group_value but a different role. Both will be granted.

  8. Multiple IdP groups can map to the same role. Useful when an institution has, e.g., both advising_freshman and advising_senior AD groups that should both get the advisor role.

  9. Save.

Step 3 — Verify with a test user

  1. Find a real user in one of the mapped groups and have them log in.
  2. In Django admin, open User Roles and search for their email.
  3. Confirm the expected role appears with source=sso and source_sso_provider set to the institution's provider.
  4. Open Audit Logs and look for the most recent role_sync entry for that user — it should show the added/removed role IDs.
  5. 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 claim is misspelled or the institution sends groups under a nested attributes key. Check the AuditLog for role_sync_no_match entries — that means CampusCore saw groups but none matched a mapping.
  • Active Directory DN strings get split into multiple groups. They shouldn't — RoleSyncService detects = 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 groups array. 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, set source='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:

  1. Reads claim_groups from the SSO provider config.
  2. 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).
  3. Looks up matching SSOGroupMapping rows.
  4. Computes the diff against the user's existing source='sso' UserRole rows.
  5. Applies the diff under select_for_update so concurrent logins don't race.
  6. Logs a role_sync audit entry containing the SSOGroupMapping IDs (never the raw group strings — avoids PII leak).
  7. 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.