Workforce via SCIM Groups
Use this when your users are your own employees, provisioned from an upstream workforce identity provider (Okta, Entra ID, etc.) via inbound SCIM, rather than external customers signing up through an Auth0 Organization. Group membership, not organization membership, is what changes as people join, move, and leave, and no organizations are involved in the model below.
A workforce setup typically provisions through a single enterprise connection (one IdP, one tenant), so the model below doesn't include a connection type at all. Instead, every rule filters on that connection's ID directly, so only groups and members from your workforce IdP are synced, not from any other connection your tenant might have.
Model
model
schema 1.1
type group # SCIM group synced from your IdP
relations
define member: [user, group#member] # direct members + nested groups
type user
Mapping
version: "1"
rules:
- name: "Add user member to group"
when: >-
input.type == "group.member.added" &&
input.data.object.member.member_type == "user" &&
input.data.object.group.connection_id == "con_YOUR_CONNECTION_ID"
variables:
group_id: input.data.object?.group?.id
member_id: input.data.object?.member?.id
tuples:
- user: "user:{{ variables.member_id }}"
relation: "member"
object: "group:{{ variables.group_id }}"
- name: "Remove user member from group"
when: >-
input.type == "group.member.deleted" &&
input.data.object.member.member_type == "user" &&
input.data.object.group.connection_id == "con_YOUR_CONNECTION_ID"
variables:
group_id: input.data.object?.group?.id
member_id: input.data.object?.member?.id
tuples:
- action: delete
user: "user:{{ variables.member_id }}"
relation: "member"
object: "group:{{ variables.group_id }}"
- name: "Add nested group to group"
when: >-
input.type == "group.member.added" &&
input.data.object.member.member_type == "group" &&
input.data.object.group.connection_id == "con_YOUR_CONNECTION_ID"
variables:
group_id: input.data.object?.group?.id
member_id: input.data.object?.member?.id
tuples:
- user: "group:{{ variables.member_id }}#member"
relation: "member"
object: "group:{{ variables.group_id }}"
- name: "Remove nested group from group"
when: >-
input.type == "group.member.deleted" &&
input.data.object.member.member_type == "group" &&
input.data.object.group.connection_id == "con_YOUR_CONNECTION_ID"
variables:
group_id: input.data.object?.group?.id
member_id: input.data.object?.member?.id
tuples:
- action: delete
user: "group:{{ variables.member_id }}#member"
relation: "member"
object: "group:{{ variables.group_id }}"
- name: "Revoke all group tuples on deletion"
when: >-
input.type == "group.deleted" &&
input.data.object?.id != nil &&
input.data.object.connection_id == "con_YOUR_CONNECTION_ID"
variables:
group_id: input.data.object.id
tuple_filters:
- object: "group:{{ variables.group_id }}"
action: delete
- object: "group:"
user: "group:{{ variables.group_id }}#member"
action: delete
tests:
- name: "group.member.added adds a user member"
input:
type: "group.member.added"
data:
object:
group:
id: "grp_engineering"
connection_id: "con_okta_scim"
member:
member_type: "user"
id: "okta|okta-scim|00u17alice"
expect_tuples:
- user: "user:okta|okta-scim|00u17alice"
relation: "member"
object: "group:grp_engineering"
- name: "group.member.added from a different connection writes nothing"
input:
type: "group.member.added"
data:
object:
group:
id: "grp_engineering"
connection_id: "con_some_other_connection"
member:
member_type: "user"
id: "okta|okta-scim|00u17alice"
expect_tuples: []
- name: "group.member.deleted removes a user member"
input:
type: "group.member.deleted"
data:
object:
group:
id: "grp_engineering"
connection_id: "con_okta_scim"
member:
member_type: "user"
id: "okta|okta-scim|00u17alice"
expect_tuples:
- user: "user:okta|okta-scim|00u17alice"
relation: "member"
object: "group:grp_engineering"
action: delete
- name: "group.member.added nests a group inside another group"
input:
type: "group.member.added"
data:
object:
group:
id: "grp_engineering"
connection_id: "con_okta_scim"
member:
member_type: "group"
id: "grp_platform"
expect_tuples:
- user: "group:grp_platform#member"
relation: "member"
object: "group:grp_engineering"
- name: "group.member.deleted removes a nested group"
input:
type: "group.member.deleted"
data:
object:
group:
id: "grp_engineering"
connection_id: "con_okta_scim"
member:
member_type: "group"
id: "grp_platform"
expect_tuples:
- user: "group:grp_platform#member"
relation: "member"
object: "group:grp_engineering"
action: delete
- name: "group.deleted revokes all group tuples"
input:
type: "group.deleted"
data:
object:
id: "grp_engineering"
connection_id: "con_okta_scim"
expect_tuples: []
expect_tuple_filters:
- object: "group:grp_engineering"
action: delete
- object: "group:"
user: "group:grp_engineering#member"
action: delete
Replace con_YOUR_CONNECTION_ID with the real connection ID from your tenant. Every rule's when guard checks connection_id before writing or deleting anything, so groups and members provisioned through any other connection in your tenant are left untouched.
Group membership changes (group.member.added/.deleted) arrive via a SCIM client whenever your IdP drives them directly. Group deletion is a separate action an admin takes directly in the Auth0 Dashboard, not part of the SCIM sync itself.
Nested groups (a group as a member of another group) are handled by the "Add nested group" and "Remove nested group" rules above: they mirror the user-member rules for member_type == "group", writing group:{member_id}#member in place of user:{member_id}, and keep the same connection_id guard. The group#member type restriction on group.member in the model is what allows this.
If you provision through more than one connection, add type connection back to the model (define member: [user, group#member] on connection, and define connection: [connection] on group), replace the connection_id guards with a group.created rule that writes both the group#member membership and the connection tuples from the event's connection_id, and see B2B via Connections for that shape.
Roles
If different group members need different access, assign roles directly to the group. See Roles: a group, at the tenant level for the model, mapping, and tests, and Roles: a group, within an organization if you also assign roles to groups from inside an organization.