Group- and property-mapping with JSON web tokens (JWT)
Purpose of this Document
These instructions are meant to help getting started with configuring group-mapping and property-mapping based on JSON web token (JWT) authentication, both for OpenID Connect (OIDC) and plain JWT authentication.
User property mappings can be configured not only based on a claim name, but also with a JSONPath (RFC 9535) reference to claim content.
Configuration
Plugins
The following plugins have to be activated. Excerpt of plugins.xml:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<pluginConfig>
<plugins>
…
<plugin name="AccessManager"/>
<!-- For OpenID Connect/OAuth2 -->
<plugin name="AccessManager_OAuth" />
<!-- For plain JWT -->
<plugin name="AccessManager_Sso_Jwt"/>
…
Access Manager Configuration
In order to activate authentication and group-mapping for JWT-based authentication, they have to be defined in the accessmanager config in the section authenticators. Excerpt of accessmanager-config.xml:
<?xml version="1.0" encoding="UTF-8"?>
<userRegistry>
<authenticators>
<!-- For OpenID Connect/OAuth2 -->
<authenticator refid="OAuth">
<groupMapping refid="OAuth" />
<propertyMapping refid="OAuth" />
</authenticator>
<!-- For plain JWT -->
<authenticator refid="JWT">
<groupMapping refid="JWT" />
<propertyMapping refid="JWT" />
</authenticator>
<!-- Further authenticator definitions, if applicable: -->
<authenticator refid="CadenzaDb">
<groupMapping refid="CadenzaDb" />
</authenticator>
</authenticators>
…
Configure Group Mapping
The configuration for group mapping differs between OIDC and plain JWT, which is why group mapping for these authentication authN/authZ procedures is documented in separate sections.
Group Mapping in OIDC
The group mapping is defined via a groupMapping element. In case of OIDC this element is placed after the authorizationProvider element in the accessmanageroauth config.
The groupMapping element must contain a claim element defining the name of the claim to be used for groups in the token.
<?xml version="1.0" encoding="UTF-8"?>
…
<groupMapping>
<claim>$SYSTEM{OAUTH_GROUP_CLAIM}</claim>
<claimValueStructure>
<!-- choice between either -->
<idList/>
<!-- or -->
<objectList groupIdKey="org_id"/>
</claimValueStructure>
…
</groupMapping>
…
Additionally, the groupMapping element can contain a simpleMappings element, which contains further simpleMapping element. Each mapping configuration in a simpleMapping element is mapped explicitly to a Cadenza group.
Lastly, there is the optional dynamicMapping that contains either true or false as value. If the dynamicMapping is not present, it is interpreted as false (no dynamic mapping active). Each claim value is transformed in to a Cadenza group-value of the same name. It is not possible to define transformation rules.
Example for a group mapping definition:
<?xml version="1.0" encoding="UTF-8"?>
…
<groupMapping>
<claim>$SYSTEM{OAUTH_GROUP_CLAIM}</claim>
<claimValueStructure>
…
</claimValueStructure>
<simpleMappings>
<simpleMapping tokenGroup="source1" cadenzaGroup="target1"/>
<simpleMapping tokenGroup="source2" cadenzaGroup="target2"/>
</simpleMappings>
<dynamicMapping>true</dynamicMapping>
</groupMapping>
…
Group Mapping in plain JWT
The group mapping is defined via a groupMapping element. In case of plain JWT authentication this element is placed after the jwtValidation (or genericJwtValidation) element in the accessmanagerssojwt config.
The group-mapping contains two configurations:
-
The definition of the claim to be used for groups in the token.
-
Information on how Cadenza should interpret the groups extracted from the token, the actual mapping.
The groups claim can be referenced as claim name, but also with a JSONPath reference to the claim content.
The mapping configuration can be declared either as static mapping or dynamic mapping:
-
static mapping: Each claim value has to be mapped explicitly to a Cadenza group. If no mapping is provided for a certain claim value, it is not mapped to any Cadenza group.
-
dynamic mapping: Each claim value is transformed in to a Cadenza group-value of the same name. It is not possible to define transformation rules.
<?xml version="1.0" encoding="UTF-8"?>
…
<groupMapping>
<!-- either-->
<claim>groups</claim>
<!-- or-->
<claimPath>$.groups</claimPath>
<staticMapping claimValue="source1" groupName="target1"/>
<staticMapping claimValue="source2" groupName="target2"/>
…
<dynamicMapping>true</dynamicMapping>
</groupMapping>
…
Examples of possible configurations (plain JWT)
Plain definition of the claim name:
<?xml version="1.0" encoding="UTF-8"?>
…
<groupMapping>
<claim>groups</claim>
…
</groupMapping>
…
Definition of the claim via JSONPath:
<?xml version="1.0" encoding="UTF-8"?>
…
<groupMapping>
<claimPath>$.groups</claimPath>
…
</groupMapping>
…
Definition of static mapping rules (if the token contains a group claim value "where", it is not mapped to a Cadenza group):
<?xml version="1.0" encoding="UTF-8"?>
…
<groupMapping>
…
<staticMapping claimValue="this" groupName="that"/>
<staticMapping claimValue="here" groupName="there"/>
<!-- default value for dynamicMapping -->
<!-- <dynamicMapping>false</dynamicMapping> -->
</groupMapping>
…
Activation of dynamic mapping (if the token contains a group claim value "where", it is mapped to a Cadenza group "where"):
<?xml version="1.0" encoding="UTF-8"?>
…
<groupMapping>
…
<dynamicMapping>true</dynamicMapping>
</groupMapping>
…
A mixture of static and dynamic mapping is possible. In this case the claim values "this" and "here" are mapped to "that" and "there" respectively and a claim value "where" is automatically mapped to a group with the name "where":
<?xml version="1.0" encoding="UTF-8"?>
…
<groupMapping>
<staticMapping claimValue="this" groupName="that"/>
<staticMapping claimValue="here" groupName="there"/>
<dynamicMapping>true</dynamicMapping>
</groupMapping>
…
Configure Property-Mapping
The principle and format for property mapping definition is the same for both OpenID Connect and plain JWT authentication. The property mappings are defined within a propertyMapping element.
User property mappings can be configured not only based on a claim name, but also with a JSONPath (RFC 9535) reference to claim content.
-
In case of OpenID Connect the element
propertyMappingis placed after thegroupMappingelement in theaccessmanageroauthconfig. -
In case of plain JWT authentication the element
propertyMappingis placed after thegroupMappingelement in theaccessmanagerssojwtconfig.
The propertyMapping element contains one or several property elements. Each property element has a name attribute and contains either a claim or a claimPath element:
<?xml version="1.0" encoding="UTF-8"?>
…
<propertyMapping>
<property name="property1">
<claim>claim1</claim>
</property>
<property name="property2">
<claimPath>$.claim2[:].sub_claim1</claimPath>
</property>
</propertyMapping>
…
Given the following hypothetical JSON web token:
{
"sub": "user1",
"nbf": 1684829639,
"exp": 1684858439,
"mail": "user1@example.com",
"name": "User One",
"iss": "idp.example.com",
"claim1": "value1"
"claim2": [
{
"sub_claim1": "value2a.1",
"sub_claim2": "value2a.2"
},
{
"sub_claim1": "value2b.1",
"sub_claim2": "value2b.1"
}
],
"groups": [
"group1",
"group2"
]
}
With the above property mapping configuration two Cadenza properties are defined: property1 and property2. property1 is a list and contains a single value: value1. property2 is a list as well and contains two values value2a.1 and value2b.1.
Logging
For easy system analysis Cadenza logs the claims it has received in the JSON web tokens. Depending on the authentication type this can be activated by setting the logging level to trace for the following loggers:
-
OpenID Connect:
net.disy.cadenza.accessmanager.oauth.OAuthUserPropertyMapper -
Plain JWT:
net.disy.cadenza.accessmanager.sso.jwt.JwtUserPropertyMapper
The logger only writes information if property mapping is active in the accessmanager config. Logger names may change without prior announcement.
|
| Logger names may change without prior announcement |
| Enabling trace-level logging will include personally-identifying information in the logs. Trace-level logging can impact performance considerably. |