Skip to main content

Custom OIDC Claim Mapping

This guide explains how to extract custom enterprise claims (e.g. Employee ID, EIN, CIN) from incoming federated OIDC identity tokens and map them to fields on your LocalUser records.


1. Defining the Custom Claims Mapper

To intercept incoming OAuth/OIDC token payloads and map custom claims to user properties, implement the OAuthClaimMapper interface defined in framework_m_core.interfaces.oauth.

Create a file in your application (e.g. src/adapters/auth/claim_mapper.py):

from typing import Any
from framework_m_core.interfaces.oauth import OAuth2UserInfo
from framework_m_core.interfaces.oauth import OAuthClaimMapper

class EnterpriseClaimMapper(OAuthClaimMapper):
"""Maps corporate and HR claims from Okta/Keycloak to LocalUser fields."""

async def map_claims(self, user_info: OAuth2UserInfo) -> dict[str, Any]:
raw_claims = user_info.raw_claims or {}

return {
"ein": raw_claims.get("custom:ein"), # Enterprise Identification Number
"cin": raw_claims.get("custom:cin"), # Corporate Identification Number
"employee_id": raw_claims.get("employeeId"), # Corporate Employee ID
}

2. Registering the Mapper in the DI Container

Once you have defined your claim mapper, override the default claim mapper registration inside your application's bootstrap phase or via project entrypoints.

Option A: Via Entry Points (Recommended)

Add the override to your app's pyproject.toml file so that Framework M detects and binds it automatically at startup:

[project.entry-points."framework_m.overrides"]
oauth_claim_mapper = "my_app.adapters.auth.claim_mapper:EnterpriseClaimMapper"

Option B: Programmatically during Bootstrap

Override the container binding inside a custom bootstrap step:

from framework_m_core.di import providers
from my_app.adapters.auth.claim_mapper import EnterpriseClaimMapper

def run(self, container: Container) -> None:
if hasattr(container, "oauth_claim_mapper"):
container.oauth_claim_mapper.override(
providers.Singleton(EnterpriseClaimMapper)
)

3. Coexistence with Dynamic Metadata Fields

In Framework M, you do not need to subclass LocalUser to add custom columns like ein or cin. Instead, register these properties dynamically at application startup using MetadataDecoratorRegistry.

When a dynamic field is registered, the SchemaMapper automatically creates corresponding columns in the database table at startup, and your EnterpriseClaimMapper can populate them:

from framework_m_core.registry import MetadataDecoratorRegistry

# Register custom attributes on LocalUser dynamically at startup
registry = MetadataDecoratorRegistry.get_instance()
registry.register_property(
doctype="LocalUser",
property_name="ein",
property_type="str",
label="Employee Identification Number",
is_nullable=True,
is_unique=True
)

During social login, the OAuthService will invoke your claim mapper, map "ein", and write it directly to the dynamically decorated columns of the LocalUser record in a single save transaction.