Claims Mapping
View SourceAfter a successful OIDC callback or JWT validation, raw provider claims
need to be transformed into an actor map. The claims_mapping config option
controls this transformation using nova_auth_claims from the nova_auth library.
Default Behaviour
With no claims_mapping configured (or #{}), the controller creates a
minimal actor:
#{
id => maps:get(~"sub", Userinfo), %% falls back to ~"email"
provider => authentik,
claims => Userinfo %% raw claims preserved
}Static Mapping
Map binary claim keys to atom actor keys:
claims_mapping => #{
~"sub" => id,
~"email" => email,
~"name" => display_name,
~"groups" => roles
}Given Authentik claims:
#{
~"sub" => ~"abc123",
~"email" => ~"jane@example.com",
~"name" => ~"Jane Doe",
~"groups" => [~"admins", ~"developers"],
~"iss" => ~"https://auth.example.com/..."
}The resulting actor is:
#{
id => ~"abc123",
provider => authentik,
email => ~"jane@example.com",
display_name => ~"Jane Doe",
roles => [~"admins", ~"developers"]
}Note that provider is always added automatically. Claims not in the mapping
(like iss) are dropped.
Callback Mapping
For complex transformations, use a {Module, Function} tuple:
claims_mapping => {my_claims, map_authentik}The function receives the raw claims map and must return an actor map:
-module(my_claims).
-export([map_authentik/1]).
map_authentik(Claims) ->
Groups = maps:get(~"groups", Claims, []),
Role = case lists:member(~"admins", Groups) of
true -> admin;
false -> user
end,
#{
id => maps:get(~"sub", Claims),
email => maps:get(~"email", Claims, undefined),
role => Role
}.Using Claims with Policies
After mapping, you can use nova_auth_policy:allow_claim/2 for authorization:
%% With static mapping: ~"groups" => roles
%% Actor has: #{roles => [~"admins", ~"developers"], ...}
nova_auth_policy:allow_claim(roles, ~"admins")
%% Checks if ~"admins" is in the actor's roles list
%% With callback mapping: groups → role atom
%% Actor has: #{role => admin, ...}
nova_auth_policy:allow_claim(role, [admin, editor])
%% Checks if role is admin or editorProvider-Specific Examples
Authentik
Authentik includes groups and entitlements in tokens:
claims_mapping => #{
~"sub" => id,
~"email" => email,
~"preferred_username" => username,
~"name" => display_name,
~"groups" => roles
}Keycloak
Keycloak nests roles under realm_access.roles. Use a callback:
claims_mapping => {my_claims, map_keycloak}
%% my_claims.erl
map_keycloak(Claims) ->
RealmAccess = maps:get(~"realm_access", Claims, #{}),
Roles = maps:get(~"roles", RealmAccess, []),
#{
id => maps:get(~"sub", Claims),
email => maps:get(~"email", Claims, undefined),
roles => Roles
}.Google claims are flat:
claims_mapping => #{
~"sub" => id,
~"email" => email,
~"name" => display_name,
~"picture" => avatar_url
}