Source property mappings
Source property mappings allow you to modify or gather extra information from sources.
This page is an overview of how property mappings work. For information about a specific protocol, refer to the protocol page:
Create a custom source property mapping
If the default source mappings are not enough, or if you need to get additional data from the source, you can create your own custom source property mappings.
Here are the steps:
- In authentik, open the Admin interface, and then navigate to Customization > Property Mappings.
- Click Create, select the property mapping type for your source, and then click Next.
- Type a unique and meaningful Name, such as
ldap-displayName-mapping:name. - In the Expression field, enter Python expressions to retrieve the value from the source. See Expression semantics below for details.
- In the source configuration, select the newly created property mapping as a User property mapping if it applies to users, or Group property mapping if it applies to groups.
How it works
Expression semantics
Each source provides the Python expression with additional data. You can import parts of that data into authentik users and groups. Assuming the source provides us with a data Python dictionary, you can write the following:
return {
"name": data.get("displayName"),
}
You can see that the expression returns a Python dictionary. The dictionary keys must match User properties or Group properties. Note that group_attributes cannot be set for users. A user property mapping can return groups, which feeds group synchronization rather than setting a property on the user.
See each source documentation for a reference of the available data. See the authentik expressions documentation for available data and functions.
Note that the list_flatten method is applied for all top-level properties, but not for attributes:
return {
"username": data.get("username"), # list_flatten is automatically applied to top-level attributes
"attributes": {
"phone": list_flatten(data.get("phoneNumber")), # but not for attributes!
},
}
Object construction process
A user or group object is constructed as follows:
- The source provides initial properties based on commonly used data.
- Each property mapping associated with the source is run and results are merged into the previous properties.
- If a property mapping throws an error, the process is aborted. If that happens inside a synchronization process, the object is skipped. If it happens during an enrollment or authentication flow, the flow is canceled.
- If a property mapping sets one attribute to
None, that attribute is then discarded.
- If the
usernamefield is not set for user objects, or thenamefield is not set for group objects, the process is aborted. - The object is created or updated. The
attributesproperty is merged with existing data if the object already exists.
Group synchronization
LDAP and SCIM sources have built-in mechanisms to get groups. This section does not apply to them.
You can write a custom property mapping to set the user's groups:
return {
"groups": data.get("groups", []),
}
The groups attribute is a special attribute that must contain group identifiers. By default, those identifiers are also used as the group name. Each identifier is then given to group property mappings as the group_id variable, if extra processing needs to happen.
An identifier has to be a simple value such as a string. Entries that are not, such as the objects some identity providers return in an OpenID Connect groups claim, are skipped, and a Configuration error event records how many were dropped.
Object-shaped OpenID Connect group claims
OpenID Connect does not define a groups claim, so providers are free to put anything in one. Some return group objects instead of plain identifiers.
The best fix is on the provider side. Configure it to return an array of stable, unique identifiers:
{
"groups": ["g1", "g2"]
}
Prefer an immutable group ID from the provider. Only use a display name if it is unique and never changes.
If the provider cannot do that, create an OAuth source property mapping and attach it to the source's User property mappings. These mappings receive the provider's response in the info variable.
The mapping below handles a groups claim holding identifiers, objects, or a mix of both:
groups = []
for group in info.get("groups", []):
if isinstance(group, dict):
# Adjust these keys for your provider. Some use "id",
# SCIM group resources use "value".
group_id = group.get("value") or group.get("id")
else:
group_id = group
if isinstance(group_id, (str, int)):
groups.append(group_id)
return {"groups": groups}
Pick a stable, unique identifier from each object, because authentik uses it to recognize the group on later logins.
The identifiers the mapping returns are merged into the groups the source already collected, not substituted for them, so the original objects are still in the list. authentik skips those and records a Configuration error event counting how many it dropped. The identifiers you extracted still synchronize.
To get rid of the event, the groups claim itself has to contain only identifiers. Either change what the provider sends, or have it emit a separate identifier-only claim and map that claim to groups.
To use the provider's group name instead of the identifier, add a second OAuth source property mapping under the source's Group property mappings. It receives group_id along with the original info data:
for group in info.get("groups", []):
if not isinstance(group, dict):
continue
# Use the same identifier keys as the user property mapping.
if (group.get("value") or group.get("id")) != group_id:
continue
# Adjust these keys for your provider.
group_name = group.get("display") or group.get("name")
if group_name:
return {"name": group_name}
break
return {"name": str(group_id)}
Skip this second mapping if the identifier already makes a reasonable group name.