goauthentik / goauthentik/authentik

SCIM: Normalize single-valued attributes from LDAP (e.g. department) by default

Open
#16,237 0 comments 0 reactions 0 assignees View on GitHub
enhancement
Dominant language
Python
Stars
25.6k
Forks
2k
Avg merge
1d 2h
Merged PRs (30d)
651

Description

**Is your feature request related to a problem? Please describe.**
When using an LDAP source for attributes, values such as department don’t normalize cleanly into SCIM mappings.
* LDAP may return ["IT"] as a list, but SCIM expects a string. This can cause updates to append values rather than replace them.
* Using list_flatten() can help, but if LDAP returns a string ("IT"), it gets treated as an iterable of characters, resulting in "I".

This requires custom mapping logic in LDAP Source Property Mappings and SCIM Provider Mappings to stay SCIM-compliant.

**Describe the solution you’d like**
It would be helpful if Authentik normalized these values automatically with safe defaults, especially for attributes that SCIM defines as single-valued (like department, title, and manager).

For example:
* If the attribute is a single-element list, unwrap it.
* If it’s already a string, preserve it.
* If it’s multi-valued, allow admins to handle that explicitly.

This way, SCIM payloads would be compliant, and administrators wouldn’t need to reimplement the same normalization logic in every deployment.

**Describe alternatives you’ve considered**
1. Flattening in LDAP mappings. Works for lists, but breaks on strings:
```python
return {
"attributes": {
"department": list_flatten(ldap.get("departmentNumber")),
},
}
```
2. Custom normalization in SCIM Provider Mappings
```python
raw = request.user.attributes.get("department")

if isinstance(raw, list):
department = raw[0] if raw else None
elif isinstance(raw, str):
department = raw.strip() or None
else:
department = None

dept_value = department or "TBD"

result["urn:ietf:params:scim:schemas:extension:enterprise:2.0:User"] = {
"department": dept_value
}
```

This works, but it’s verbose and requires every admin to maintain their own version.

Both approaches are workarounds — they solve the issue but add complexity.

**Additional context**

📉 Before (broken payloads):

```json
"urn:ietf:params:scim:schemas:extension:enterprise:2.0:User": {
"department": ["IT"]
}
```
or:
```json
"urn:ietf:params:scim:schemas:extension:enterprise:2.0:User": {
"department": "I"
}
```
✅ After (with workaround):
```json
"urn:ietf:params:scim:schemas:extension:enterprise:2.0:User": {
"department": "IT"
}
```

Since the SCIM RFC (7643) defines department as a single string, it feels like Authentik could handle this more gracefully by default. That would make life easier for folks syncing attributes from LDAP into SCIM without needing extra mapping code.

Thanks for considering, and happy to test or help refine if this gets picked up! If the team feels this belongs in docs rather than code, I’d be glad to help draft examples” to show you’re open either way?

Contributor guide

Open the contributing guide

Research direction

Start by tracing LDAP Source Property Mappings and SCIM Provider Mappings, then compare their value handling with the SCIM RFC 7643 definitions for single-valued attributes. Done means single-element lists become strings, existing strings remain intact, and multi-valued values remain available for explicit administrator handling.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
backend
Issue type
Feature
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.