Understanding property mappings in Authentik: the Python expressions that determine OIDC claim contents, built-in mappings for profile, email, and groups, custom scope mappings, and how to debug mappings with the tester and token preview.

In episode 8, you successfully issued tokens through the OAuth2 provider. But the token is like an empty suitcase: all the user's identity is already available in Authentik, yet nothing has decided what goes inside.
That decision is in the hands of property mappings. To use an analogy: the token is the suitcase, and the property mapping is the courier choosing which items may be carried out. Applications can only read the data sent to them — so understanding mappings is how you control what each application sees, and what leaks out.
A property mapping is a Python expression that returns a value to be sent to an application. The mapping type depends on the provider:
There's one important rule: returning None means the mapping is skipped — its value doesn't appear at all. The context available in expressions generally includes user, request, and provider (for scope mappings), plus the same helpers as in episode 6: regex_match, regex_replace, and ak_is_group_member.
Authentik ships several standard scope mappings you can use right away:
The profile scope on an OAuth2 provider points to the profile mapping, the email scope points to the email mapping, and so on. So when an application requests a scope, this is the content that automatically comes along.
Important
Since release 2025.10, the email_verified claim defaults to False. Authentik can't verify on its own whether a user's email is truly verified, and claiming otherwise could create a security risk. Some applications refuse login when this claim is False — the solution is a custom mapping, not just changing the default.
A scope mapping usually returns a dictionary. Each key-value pair becomes a claim in the token. The simplest example: a custom mapping that sends the user's group list.
return {
"groups": [group.name for group in request.user.ak_groups.all()],
}The ak_groups.all() relation fetches all groups the user is a member of, then the list comprehension collects their names. As a result, the token contains a groups claim with a list like ["dev", "ops"] that applications can use to assign roles.
To create your own mapping: go to Customization > Property Mappings, click Create, then choose the Scope Mapping type. There are three important fields: the name, the scope it will fill, and the expression.
A classic example that's often needed: fixing the email claim so email_verified follows the status you store as a user attribute.
return {
"email": request.user.email,
"email_verified": request.user.attributes.get("email_verified", False),
}Or pulling the custom attributes you set in episode 5:
return {
"department": request.user.attributes.get("department", "unknown"),
}Tip
Use .get(key, default) to read custom attributes. Accessing a non-existent attribute directly triggers an error like KeyError, and a single error in a mapping can fail the entire token issuance.
Scope and claim are two different things that are related:
openid, profile, email, or a custom scope you create.An application requesting the profile scope receives claims from the mapping bound to that scope. If you create a custom scope named roles with a mapping that returns a role list, the application must request the roles scope for its claims to appear. Unconfigured requested scopes are rejected — and this is one of the integration error sources you'll encounter in episode 10.
Also keep in mind: if a client doesn't request any scope, Authentik treats all default scopes as requested (the lesson from episode 8). Custom scopes aren't part of that behavior.
Once a mapping is created, add it to the OAuth2 provider in the scope mappings section. The full flow:
roles.roles scope when initializing its OIDC client.For stricter scope-based authorization decisions, episode 6 also provides a tool: an expression policy can check requested scopes via a dedicated context — for example, denying an admin scope unless the user is a member of a specific group.
Mappings are code — so they can error, and debugging them needs the right tools:
Warning
Never put sensitive information like password hashes, secret tokens, or excessive personal data into claims. Claims that go into tokens are read by applications and can leak through that application's logs. Send only what's actually needed.
Key points from this episode:
email_verified defaults to False.Now the token suitcase is filled exactly as you want. All that's left is practicing it on real applications. In episode 10, you'll integrate Grafana, Nextcloud, Portainer, and Gitea with Authentik's OIDC — complete with a login flow walkthrough and troubleshooting the errors that most often make admins scratch their heads.