A Nextcloud app that maps multiple OIDC token claims to Nextcloud groups via configurable rules. Works with any identity provider through the user_oidc app.
- 5 rule types — direct, prefix, map, conditional, template
- Dot-notation claim paths — access any nested token field
- Additive or replace mode — merge with or override existing groups
- Admin UI + OCC commands — configure via browser or CLI
- Test command — validate rules against sample tokens before going live
- Zero config on user_oidc — works with any provider setup
- The problem
- Quick start
- Rule types
- Configuration
- Configuring user_oidc
- Admin settings
- OCC commands
- How it works
- Installation
- Development
- Troubleshooting
- Contributing
Your identity provider sends a JWT token like this:
{
"sub": "jdoe",
"email": "jdoe@example.com",
"department": "Engineering",
"roles": ["admin", "editor"],
"organization": "corp.example.com",
"userType": "INTERNAL"
}With user_oidc alone, you can map one claim to groups (mappingGroups). But what if you need groups from department, roles, organization, and userType all at once?
This app solves that. Configure rules to map any number of claims to Nextcloud groups:
| Without this app | With this app |
|---|---|
| 1 claim → groups | N claims → groups via configurable rules |
roles → ["admin", "editor"] |
department → Engineering |
roles → role_admin, role_editor |
|
organization → Staff (via lookup table) |
|
userType == INTERNAL → Internal-Users |
- Nextcloud 29 -- 32
- PHP 8.1+
- user_oidc app installed and enabled
# Download and extract
cd /var/www/html/custom_apps/
wget https://github.com/strobelpierre/nextcloud_oidc_groups_mapping/releases/latest/download/oidc_groups_mapping.tar.gz
tar xzf oidc_groups_mapping.tar.gz
# Enable
php occ app:enable oidc_groups_mapping
# Configure rules
php occ oidc-groups:set '{
"version": 1,
"mode": "additive",
"rules": [
{"id": "departments", "type": "direct", "enabled": true, "claimPath": "department", "config": {}},
{"id": "user-roles", "type": "prefix", "enabled": true, "claimPath": "roles", "config": {"prefix": "role_"}}
]
}'
# Test with a sample token
php occ oidc-groups:test --token '{"department":"Engineering","roles":["admin","editor"]}'Using the JWT token example above:
| Type | What it does | Example | Result |
|---|---|---|---|
direct |
Claim value becomes group name | department |
Engineering |
prefix |
Prefix each value | roles with prefix role_ |
role_admin, role_editor |
map |
Lookup table | organization: corp.example.com → Staff |
Staff |
conditional |
If claim matches condition → assign groups | userType equals INTERNAL |
Internal-Users |
template |
String template with {value} placeholder |
department with dept_{value} |
dept_Engineering |
| Operator | Description | Example |
|---|---|---|
equals |
Exact string match | userType equals "EXTERNAL" |
contains |
Array contains value | roles contains "admin" |
regex |
Regex match (with delimiters) | email matches /@example\.com$/ |
Rules are stored as JSON in IAppConfig. You can configure them via Admin Settings or OCC commands.
{
"version": 1,
"mode": "additive",
"rules": [
{
"id": "departments",
"type": "template",
"enabled": true,
"claimPath": "department",
"config": { "template": "dept_{value}" }
},
{
"id": "user-roles",
"type": "prefix",
"enabled": true,
"claimPath": "roles",
"config": { "prefix": "role_" }
},
{
"id": "org-mapping",
"type": "map",
"enabled": true,
"claimPath": "organization",
"config": {
"values": {
"corp.example.com": "Staff",
"partner.example.com": "Partners"
},
"unmappedPolicy": "ignore"
}
},
{
"id": "internal-flag",
"type": "conditional",
"enabled": true,
"claimPath": "userType",
"config": {
"operator": "equals",
"value": "INTERNAL",
"groups": ["Internal-Users"]
}
}
]
}| Mode | Behavior |
|---|---|
additive (default) |
Rule-produced groups are merged with existing groups from mappingGroups |
replace |
Only rule-produced groups are kept. If rules produce nothing, falls back to existing groups (safety net) |
Dot-notation paths resolve nested token claims:
department→token.departmentextended_attributes.auth.permissions→token.extended_attributes.auth.permissions
URL-style claim keys are also supported (e.g., https://idp.example.com/claims/domain).
When a map rule encounters a value not in the lookup table:
| Policy | Behavior |
|---|---|
ignore |
Value is silently skipped |
passthrough |
Original claim value is used as group name |
This app works alongside user_oidc's built-in group mapping. Here's how they interact:
When a user logs in via OIDC, user_oidc dispatches an event for group mapping. This app intercepts that event, applies your rules against the full JWT token, and produces groups.
The mappingGroups setting in your user_oidc provider configuration controls what user_oidc extracts before this app runs:
mappingGroups setting |
What happens |
|---|---|
Set to a claim (e.g., groups) |
user_oidc extracts groups from that claim first. This app then merges (additive) or replaces them. |
| Empty or claim doesn't exist | No groups extracted by user_oidc. This app produces all groups from rules. |
If your IdP token has a groups claim and you want to combine it with advanced rules:
- In user_oidc provider settings, set
mappingGroupstogroups(or your claim name) - Configure this app in
additivemode — rule-produced groups merge with the native ones
If you want full control via rules only:
- Leave
mappingGroupsat its default — it doesn't matter what it's set to - Configure all your mapping rules in this app
- Use
replacemode if you want only rule-produced groups
- No special user_oidc settings are required — the app works with any provider configuration
- The app accesses the full JWT token, not just the claim pointed to by
mappingGroups - Other attribute mappings (
mappingDisplayName,mappingEmail, etc.) are unaffected
Configure rules through the Nextcloud admin panel under Administration → OIDC Groups Mapping.
# List configured rules
php occ oidc-groups:list
# Set rules from JSON
php occ oidc-groups:set '{"version":1,"mode":"additive","rules":[...]}'
# Test rules against a sample token
php occ oidc-groups:test --token '{"department":"IT","roles":["admin","editor"]}'
# Test with existing groups (to see merge behavior)
php occ oidc-groups:test --token '{"department":"IT"}' --existing '["users"]'This app listens to the AttributeMappedEvent dispatched by user_oidc during login. When the mappingGroups attribute is being processed, it:
- Loads mapping rules from
IAppConfig - Resolves claim values from the token using dot-notation paths
- Applies each enabled rule to produce groups
- Merges or replaces the group list depending on the mode
- Calls
setValue()andstopPropagation()on the event
cd /var/www/html/custom_apps/
wget https://github.com/strobelpierre/nextcloud_oidc_groups_mapping/releases/latest/download/oidc_groups_mapping.tar.gz
tar xzf oidc_groups_mapping.tar.gz
php occ app:enable oidc_groups_mappingcd /var/www/html/custom_apps/
git clone https://github.com/strobelpierre/nextcloud_oidc_groups_mapping.git oidc_groups_mapping
cd oidc_groups_mapping && composer install --no-dev
php occ app:enable oidc_groups_mappingcomposer install
composer test:unit # PHPUnit
composer psalm # Static analysis
composer cs:check # Code style check (requires vendor-bin setup)
composer cs:fix # Fix code style
composer lint # PHP syntax checkmkdir -p vendor-bin/cs-fixer
cd vendor-bin/cs-fixer
composer require nextcloud/coding-standard
cd ../..A Docker Compose setup with Keycloak is available in dev/:
cd dev && docker compose up -dmake appstore
# Output: build/oidc_groups_mapping.tar.gz- Ensure
user_oidcis installed and enabled - Verify claim paths match your IdP token structure using
php occ oidc-groups:test - Check Nextcloud logs for
oidc_groups_mappingmessages
- Verify rules are enabled (
"enabled": true) - Ensure the JSON is valid via the admin settings UI or
php occ oidc-groups:list - For conditional rules with
regexoperator, ensure the regex pattern is valid (including delimiters)
Contributions are welcome! See CONTRIBUTING.md for guidelines.
AGPL-3.0-or-later -- see LICENSE for details.