How to Map User Identities¶
TL;DR¶
Add identities to LocalUser objects so Veza can correlate users across integrated
systems. Use map_to_identity: true in field_mapping for simple cases; use a
record-transform hook for conditional or multi-source logic.
Background¶
When Veza ingests users from multiple systems, it uses identities to link the same person across those systems. For example, a user in your app and the same user in your IdP can be correlated if they share an email address stored as an identity on both records.
Identities are attached to LocalUser objects via add_identity(). Each user can have
multiple identities — typically an email address, a username, or an external identifier.
Prerequisites¶
An integration with
destination: LocalUserin at least one sourceSource data that includes a field suitable for identity correlation (email, username, UPN, etc.)
Option 1: Declarative config (recommended for simple cases)¶
Add map_to_identity: true to any field_mapping entry. OAA Runner will call
add_identity() with that field’s value for every user record.
sources:
users:
destination: LocalUser
source_type: custom
connection: my_api
params:
resource: /users
field_mapping:
user_id:
destination_field: unique_id
name:
destination_field: name
email:
destination_field: email
map_to_identity: true # adds email as an identity
Multiple identities¶
Mark multiple fields with map_to_identity: true to add more than one identity per user:
field_mapping:
user_id:
destination_field: unique_id
email:
destination_field: email
map_to_identity: true # primary identity
upn:
destination_field: upn
custom_type: STRING
map_to_identity: true # secondary identity
username:
destination_field: username
custom_type: STRING
map_to_identity: true # tertiary identity
With source_field override¶
If the source column name differs from the destination field name, use source_field:
field_mapping:
email_address: # source column name
destination_field: email
source_field: email_address # read from this column
map_to_identity: true
Behaviour¶
Empty strings and
Nonevalues are silently skippedThe field is still mapped to the destination OAA property as normal —
map_to_identityis additive, not exclusiveOnly applies to
LocalUserdestination sources; ignored forLocalGroup,LocalRole,Resource, andEnrichedEntity
Option 2: Record-transform hook (for conditional or computed identities)¶
Use a hook when you need to:
Skip identity assignment based on a condition
Compute an identity from multiple fields
Read from a secondary data source
# modules/hooks.py
import logging
from oaa.hooks.decorators import OAAHookEvent, record_transform_non_stream
logger = logging.getLogger(__name__)
@record_transform_non_stream()
def add_email_identity(record, provider, **kwargs):
user = provider.local_users.get(record['unique_id'])
if not user:
return
if record.get('email'):
user.add_identity(record['email'])
Register the hook in config.yaml:
hooks:
- add_email_identity
When to prefer hooks over config¶
Scenario |
Use |
|---|---|
Map a single column to identity for all records |
|
Map multiple columns to identity for all records |
|
Skip identity assignment based on a field value |
Hook |
Compute identity from two or more fields |
Hook |
Look up identity from a second data source |
Hook |
Verify identities are set¶
After running the integration in --save-json mode, confirm identities appear in the
payload:
oaa app run --config=config.yaml --save-json
jq '.local_users[] | {name: .name, identities: .identities}' output.json
Expected output:
{
"name": "Alice Smith",
"identities": ["alice@example.com"]
}