Elevate ร— IDHub
๐Ÿ“Š Capability Map
Integration Playground

Hands-On Labs

10 labs covering every integration concern. Each tells the business story up front โ€” expand Technical Details for config, API calls, and implementation guidance.

1

OIDC Client Setup

E2, E6

Business Story: Elevate registers as an OIDC Relying Party with IDHub. This is the "handshake" โ€” after it's done, Elevate users can sign in with any IDHub identity provider, and Elevate gets back a JWT with organization context, roles, and entitlements.

Elevate App
โ†’ authorize
IDHub LoginUI
โ†’ code
Elevate App
โ†’ token
JWT with orgs + perms
โš™๏ธ Technical Details

OIDC Client Configuration

client_idelevate-simulator
grant_typeauthorization_code with PKCE (S256)
redirect_urihttp://localhost:3003/auth/callback
scopesopenid profile email organizations entitlements
authorize_endpoint/api/oidc/{realm}/authorize
token_endpoint/api/oidc/{realm}/token

MGM App Resource

In the MGM App, this OIDC client is managed as a client-application resource in the b2b realm. It defines the scopes available and which organization-level permissions can be requested.

{
  "type": "client-application",
  "id": "elevate-simulator",
  "data": {
    "name": "Elevate Simulator",
    "grantTypes": ["authorization_code"],
    "scopes": ["openid", "profile", "email", "organizations", "entitlements"],
    "redirectUris": ["http://localhost:3003/auth/callback"]
  }
}
2

Domain-Based Org Resolution

E2, E3, E8

Business Story: When a user enters their email, IDHub matches the domain to an organization. @berlin-med.de โ†’ Berlin Medical Center. This determines which login method they see, which org they join, and which roles become available.

user@berlin-med.de
โ†’ domain lookup
Berlin Medical Center
โ†’ has federation?
โ†’ Keycloak SSO

Behind the Scenes

Domain resolution runs server-side inside LoginUI via a Next.js Server Action โ€” no public API endpoint is exposed.

// LoginUI server action (runs on server only)
"use server";
await resolveOrganizationsByDomain(domain, realm);
// โ†’ { matched, orgId, orgName, identityProviderType, autoJoin }
โš™๏ธ Technical Details

Server Action (replaces former public endpoint)

// Next.js Server Action โ€” never exposed as HTTP route
import { resolveDomainAction } from "@/app/actions/resolve-domain";

const result = await resolveDomainAction({ domain: "berlin-med.de", realm: "b2b" });
// โ†’ { matched: true, orgId: "berlin-medical-center",
//    orgName: "Berlin Medical Center",
//    identityProviderType: "OIDC",
//    autoJoin: true, autoJoinRole: "member" }

org-domain Resource (MGM App)

Each domain mapping is an org-domain resource under the organization:

{
  "type": "org-domain",
  "id": "berlin-med.de",
  "path": "/orgs/berlin-medical-center/domains",
  "data": {
    "domain": "berlin-med.de",
    "verified": true
  }
}

Resolution Priority

  1. Exact domain match โ†’ single org โ†’ auto-join
  2. Multi-org match โ†’ user picks from selector
  3. No match โ†’ continue without org (or search, if E8 is enabled)
3

Organization IDP Federation

E2

Business Story: Berlin Medical Center has its own Azure AD. Instead of creating separate passwords, their staff click "Sign In" and are seamlessly redirected to their corporate login. After authenticating there, they return to Elevate with full org context โ€” no extra steps.

Elevate
โ†’ domain match
IDHub LoginUI
โ†’ redirect
Keycloak (corp SSO)
โ†’ back with token
User + Org context

Try It โ€” Federated Login

Sign in as dr.mueller@berlin-med.de (password: test1234). You'll be redirected through Keycloak, then back here with organization context.

โš™๏ธ Technical Details

identity-provider Resource

Federation is configured via an identity-provider resource with type federation-oidc:

{
  "type": "identity-provider",
  "id": "berlin-med-keycloak",
  "data": {
    "name": "Berlin Medical Center SSO",
    "type": "federation-oidc",
    "issuer": "http://localhost:8080/realms/berlin-med",
    "clientId": "idhub-federation",
    "clientSecret": "...",
    "scopes": ["openid", "profile", "email"],
    "autoCreateAccount": true,
    "claimMapping": {
      "email": "email",
      "given_name": "given_name",
      "family_name": "family_name"
    }
  }
}

Flow Sequence

  1. User enters dr.mueller@berlin-med.de
  2. LoginUI calls resolve-domain โ†’ finds Berlin Medical Center + IDP
  3. LoginUI redirects to Keycloak's /authorize endpoint
  4. User authenticates with Keycloak
  5. Keycloak redirects back to LoginUI with auth code
  6. LoginUI exchanges code, creates/updates IDHub account
  7. LoginUI issues IDHub token with org context โ†’ redirect to Elevate
4

Role Templates & Assignment

E3, E4, E5

Business Story: Bayer defines roles once โ€” "Admin", "HCP", "Wholesaler" โ€” and every organization inherits them. When the first user registers at an org, they become Admin automatically. Subsequent users get a default role. This scales to 8,000+ orgs without per-org configuration.

RoleSelf-Registrable?Default?Permissions
adminNo (auto-promoted)NoAll modules + member management
hcpYesYes โœ“Portal + Club Bayer
wholesalerYesNoPortal + Club Bayer + My-Orders
guestYesNoPortal only (read-only dashboard)
โš™๏ธ Technical Details

realm-role-template Resource

{
  "type": "realm-role-template",
  "id": "hcp",
  "data": {
    "name": "Healthcare Professional",
    "isDefault": true,
    "isSelfRegistrable": true,
    "clientPermissions": {
      "elevate-simulator": ["dashboard:read", "training:read"]
    }
  }
}

Auto-Promote First Member (E3)

Configured in realm-org-settings:

{
  "type": "realm-org-settings",
  "data": {
    "autoPromoteFirstMember": true,
    "adminRoleId": "admin"
  }
}

How Roles Flow into the Token

  1. User joins org โ†’ assigned default role (or admin if first)
  2. Role's clientPermissions determine per-module access
  3. Token includes organizations[].role and client_permissions[]
5

Attribute Collection & Schemas

E7, E9, E12

Business Story: Bayer needs extra information from members โ€” billing address for wholesalers, specialty for HCPs. Attribute schemas define what data to collect, at what level (realm-wide vs. per-org vs. per-role), and whether it's mandatory. This drives both UX (what forms show) and access control (My-Orders needs an address).

Realm Level
Applies to ALL members
e.g., email, phone
โ†“ inherited by
Organization Level
Org-specific overrides
e.g., billingAddress mandatory
โ†“ inherited by
Role Level
Role-specific fields
e.g., wholesaler: shippingAddress
โš™๏ธ Technical Details

3-Tier Attribute Schema Inheritance

// realm-org-settings โ†’ memberAttributeSchema (realm-wide)
{
  "memberAttributeSchema": {
    "fields": {
      "phone": { "type": "string", "required": false, "label": "Phone Number" },
      "specialty": { "type": "enum", "required": false, "options": ["cardiology", "oncology", "general"] }
    }
  }
}

// org-level override (e.g., for Munich Pharma)
{
  "memberAttributeOverrides": {
    "billingAddress": { "type": "address", "required": true }
  }
}

// role-level (set in realm-role-template)
{
  "roleAttributes": {
    "shippingAddress": { "type": "address", "required": true }
  }
}

Attribute Conditions for Entitlements (E7)

Module access can depend on attributes, not just role:

// In org-client-permission for My-Orders:
{
  "attributeConditions": [
    { "field": "billingAddress", "operator": "exists" },
    { "field": "shippingAddress", "operator": "exists" }
  ]
}
6

Module Entitlements & Permission Gating

E6, E7

Business Story: Users sign in once and get access to multiple modules โ€” Portal, Club Bayer, My-Orders โ€” based on their role and attributes. A wholesaler with a billing address sees all three. An HCP without an address sees only Portal and Club Bayer. Elevate reads permissions from the JWT โ€” no extra API calls needed.

๐Ÿ  Portal Always

Dashboard access for all authenticated members

๐ŸŽ“ Club Bayer HCP + Wholesaler

Training content, requires training:read

๐Ÿ“ฆ My-Orders Wholesaler + Address

Order management, requires orders:create + billing address

โš™๏ธ Technical Details

org-client-permission Resource

Defines what permissions each client app can request:

{
  "type": "org-client-permission",
  "id": "elevate-simulator",
  "data": {
    "clientApplicationId": "elevate-simulator",
    "permissions": ["dashboard:read", "training:read", "orders:create", "orders:read"],
    "attributeConditions": {
      "orders:create": [
        { "field": "billingAddress", "operator": "exists" }
      ]
    }
  }
}

JWT Token Structure

After login, the JWT contains computed permissions:

{
  "sub": "abc123",
  "email": "user@berlin-med.de",
  "organizations": [{
    "organizationId": "berlin-medical-center",
    "organizationName": "Berlin Medical Center",
    "role": "wholesaler",
    "basePermissions": ["member:read"]
  }],
  "client_permissions": ["dashboard:read", "training:read", "orders:create"],
  "entitlements": {
    "portal": { "eligible": true },
    "club-bayer": { "eligible": true },
    "my-orders": { "eligible": true }
  }
}

Client-Side Gating Pattern

// In Elevate's frontend:
const token = decodeJWT(accessToken);
const perms = token.client_permissions || [];

// Gate a module
if (perms.includes('orders:create')) {
  showMyOrdersModule();
} else {
  showUpgradePrompt('Complete your billing address to access My-Orders');
}
7

Member Rules Engine

E11

Business Story: Instead of manually assigning roles, Bayer defines rules. "If the user's business type is wholesaler, assign the wholesaler role." These rules run automatically when a member is added or attributes change โ€” keeping 8,000+ organizations perfectly consistent without admin intervention.

RuleConditionAction
wholesaler-auto-assignbusinessType == "wholesaler"Assign role: wholesaler
hcp-auto-assignbusinessType == "hcp"Assign role: hcp
guest-fallbackNo other rule matchesAssign role: guest
โš™๏ธ Technical Details

member-rule Resource

{
  "type": "member-rule",
  "id": "wholesaler-auto-assign",
  "data": {
    "name": "Auto-assign wholesaler role",
    "priority": 100,
    "condition": {
      "field": "memberAttributes.businessType",
      "operator": "eq",
      "value": "wholesaler"
    },
    "action": {
      "type": "assign-role",
      "roleId": "wholesaler"
    }
  }
}

Rule Evaluation Flow

  1. Member joins org or attributes change โ†’ trigger evaluation
  2. Rules sorted by priority (highest first)
  3. First matching rule wins โ€” action applied
  4. If no rule matches, default role used (if configured)
  5. Role change โ†’ permissions recalculated โ†’ next token reflects change

Where to Configure

Rules are realm-level resources. Manage in: MGM App โ†’ b2b realm โ†’ Member Rules.

8

Token Structure, Claims & Custom Mappers

E6, E14

Business Story: The IDHub JWT is the "passport" that Elevate reads. It contains who the user is, which organization they belong to, what role they have, and what modules they can access โ€” all in one standard token. No extra API calls needed for basic authorization.

Custom Token Mappers (E14): Beyond the standard OIDC claims, IDHub injects per-client custom claims configured through the MGM App. For the Elevate Simulator, the access token includes org_department, b2b_tier, and roles โ€” and the aud claim is extended with https://api.elevate.bayer.com. These are evaluated server-side from account data at every token issuance.

Sign in to see your live JWT and decoded claims.

โš™๏ธ Technical Details

Standard OIDC Claims

ClaimTypeDescription
substringIDHub account ID (unique per user)
emailstringUser's email address
namestringFull name
given_namestringFirst name
family_namestringLast name
realmstringIDHub realm (e.g., "b2b")
org_idstringActive organization ID
organizationsarrayAll memberships with role + permissions
client_permissionsarrayEffective permissions for this client app
entitlementsobjectModule eligibility map

๐Ÿ”ง Custom Claims from Token Mappers E14

These claims are injected per-client by Token Mappers configured on the client-application resource. They are evaluated server-side by Profile API's resolve-token-claims task at token issuance time โ€” the client never controls what claims appear. Mappers are configured in MGM App โ†’ Client Applications โ†’ Token Mappers tab.

ClaimMapper TypeTargetDescription
org_departmentaccount-attributeaccess_token Reads mixed.department from the user's mixed account
b2b_tierhardcodedaccess_token Static value "enterprise" โ€” identifies this as an enterprise B2B client
rolesaccount-tagsboth Account tags filtered by role:* โ€” emits role tags as an array
display_nameexpressionid_token Computed: firstName + lastName or falls back to email
aud (extended)audienceaccess_token Appends https://api.elevate.bayer.com to the audience array

Supported Mapper Types

TypeDescription
account-attributeReads a dot-path field from the mixed account (e.g., mixed.department)
hardcodedEmits a static value (string, number, boolean) for every token
account-tagsEmits the account's tags (optionally filtered by a glob pattern)
expressionEvaluates a safe expression โ€” dot-paths, ternary, comparisons, string methods
profile-attributeReads a field from a specific identity provider profile (e.g., SAP CDC)
audienceAppends additional audience URIs to the aud claim

Token Lifecycle

  • Access tokens are short-lived (configurable, typically 5โ€“15 min)
  • Refresh tokens enable silent renewal
  • Role/permission changes take effect at next token refresh
  • Custom claims re-evaluated on every token issuance (always up-to-date)
  • Token is signed with RS256 โ€” verify with JWKS endpoint
  • Token size monitored: warning at 4 KB, hard limit at 8 KB
9

Profile API Integration

E9, E12

Business Story: Sometimes Elevate needs more than what's in the JWT โ€” full profile data, organization details, or to update member attributes. The Profile API (GraphQL) provides this. It's the "deep data layer" behind the token.

Sign in to query the Profile API for your live account data.

โš™๏ธ Technical Details

GraphQL Endpoint

Profile API runs on port 4000 (local) or via the proxy at /api/profile.

Example Queries

# Get entitlements for current user
query {
  getAccountEntitlements(clientApplicationId: "elevate-simulator") {
    accountId
    organizations {
      organizationId
      organizationName
      role
      basePermissions
      clientEntitlements {
        clientApplicationId
        permissions
        features
      }
    }
  }
}

# Get account by profile
query {
  getAccountByProfile(provider: "SAP_CDC", profileId: "user123") {
    accountId
    profiles { provider profileId data }
    mixedAccount { email firstName lastName }
  }
}

Authentication

Profile API requires a valid IDHub access token in the Authorization: Bearer {token} header. The simulator proxies this through /api/profile.

10

MyAccount & MGM App

E4, E9, E10

Business Story: Two companion apps complete the Elevate integration. MyAccount is the user-facing self-service portal โ€” users update their profile, manage org attributes, and review consent. MGM App is the admin tool โ€” Bayer admins configure realms, roles, rules, schemas, and organizations.

โš™๏ธ Technical Details

MyAccount Context Configuration

MyAccount uses context-based routing. For B2B realm:

// contextMapping.json
{
  "b2b": {
    "realm": "b2b",
    "name": "B2B Portal",
    "profileApiUrl": "http://localhost:4000/graphql",
    "features": {
      "organizations": true,
      "memberAttributes": true,
      "roleManagement": true
    }
  }
}

// Access via: http://localhost:3000?context=b2b

MGM App โ€” Key B2B Admin Flows

  • Realm โ†’ Organizations: Browse/search the 8K+ org list
  • Realm โ†’ Role Templates: Define roles + client permissions
  • Realm โ†’ Member Rules: Configure rule-based role assignment
  • Realm โ†’ Org Settings: Set realm-wide defaults + overrides
  • Realm โ†’ Client Applications: Manage OIDC clients like Elevate