9.4 KiB
9.4 KiB
Tanflow SSO User Data Mapping
This document outlines all user information available from Tanflow IAM Suite and how it maps to our User model for user creation.
Tanflow Userinfo Endpoint Response
Tanflow uses OpenID Connect (OIDC) standard claims via the /protocol/openid-connect/userinfo endpoint. The following fields are available:
Standard OIDC Claims (Available from Tanflow)
| Tanflow Field | OIDC Standard Claim | Type | Description | Currently Extracted |
|---|---|---|---|---|
sub |
sub |
string | REQUIRED - Subject identifier (unique user ID) | ✅ Yes (as oktaSub) |
email |
email |
string | Email address | ✅ Yes |
email_verified |
email_verified |
boolean | Email verification status | ❌ No |
preferred_username |
preferred_username |
string | Preferred username (fallback for email) | ✅ Yes (fallback) |
name |
name |
string | Full display name | ✅ Yes (as displayName) |
given_name |
given_name |
string | First name | ✅ Yes (as firstName) |
family_name |
family_name |
string | Last name | ✅ Yes (as lastName) |
phone_number |
phone_number |
string | Phone number | ✅ Yes (as phone) |
phone_number_verified |
phone_number_verified |
boolean | Phone verification status | ❌ No |
address |
address |
object | Address object (structured) | ❌ No |
locale |
locale |
string | User locale (e.g., "en-US") | ❌ No |
picture |
picture |
string | Profile picture URL | ❌ No |
website |
website |
string | Website URL | ❌ No |
profile |
profile |
string | Profile page URL | ❌ No |
birthdate |
birthdate |
string | Date of birth | ❌ No |
gender |
gender |
string | Gender | ❌ No |
zoneinfo |
zoneinfo |
string | Timezone (e.g., "America/New_York") | ❌ No |
updated_at |
updated_at |
number | Last update timestamp | ❌ No |
Custom Tanflow Claims (May be available)
These are custom claims that Tanflow may include based on their configuration:
| Tanflow Field | Type | Description | Currently Extracted |
|---|---|---|---|
employeeId |
string | Employee ID from HR system | ✅ Yes |
employee_id |
string | Alternative employee ID field | ✅ Yes (fallback) |
department |
string | Department/Division | ✅ Yes |
designation |
string | Job designation/position | ✅ Yes |
title |
string | Job title | ❌ No |
designation |
string | Job designation/position | ✅ Yes (as designation) |
employeeType |
string | Employee type (Dealer, Full-time, Contract, etc.) | ✅ Yes (as jobTitle) |
organization |
string | Organization name | ❌ No |
division |
string | Division name | ❌ No |
location |
string | Office location | ❌ No |
manager |
string | Manager name/email | ❌ No |
manager_id |
string | Manager employee ID | ❌ No |
cost_center |
string | Cost center code | ❌ No |
hire_date |
string | Date of hire | ❌ No |
office_location |
string | Office location | ❌ No |
country |
string | Country code | ❌ No |
city |
string | City name | ❌ No |
state |
string | State/Province | ❌ No |
postal_code |
string | Postal/ZIP code | ❌ No |
groups |
array | Group memberships | ❌ No |
roles |
array | User roles | ❌ No |
Current Extraction Logic
Location: Re_Backend/src/services/auth.service.ts → exchangeTanflowCodeForTokens()
const userData: SSOUserData = {
oktaSub: tanflowSub, // Reuse oktaSub field for Tanflow sub
email: tanflowUserInfo.email || tanflowUserInfo.preferred_username || '',
employeeId: tanflowUserInfo.employeeId || tanflowUserInfo.employee_id || undefined,
firstName: tanflowUserInfo.given_name || tanflowUserInfo.firstName || undefined,
lastName: tanflowUserInfo.family_name || tanflowUserInfo.lastName || undefined,
displayName: tanflowUserInfo.name || tanflowUserInfo.displayName || undefined,
department: tanflowUserInfo.department || undefined,
designation: tanflowUserInfo.designation || undefined, // Map designation to designation
phone: tanflowUserInfo.phone_number || tanflowUserInfo.phone || undefined,
// Additional fields
manager: tanflowUserInfo.manager || undefined,
jobTitle: tanflowUserInfo.employeeType || undefined, // Map employeeType to jobTitle
postalAddress: tanflowUserInfo.address ? (typeof tanflowUserInfo.address === 'string' ? tanflowUserInfo.address : JSON.stringify(tanflowUserInfo.address)) : undefined,
mobilePhone: tanflowUserInfo.mobile_phone || tanflowUserInfo.mobilePhone || undefined,
adGroups: Array.isArray(tanflowUserInfo.groups) ? tanflowUserInfo.groups : undefined,
};
User Model Fields Mapping
Location: Re_Backend/src/models/User.ts
| User Model Field | Tanflow Source | Required | Notes |
|---|---|---|---|
userId |
Auto-generated UUID | ✅ | Primary key |
oktaSub |
sub |
✅ | Unique identifier from Tanflow |
email |
email or preferred_username |
✅ | Primary identifier |
employeeId |
employeeId or employee_id |
❌ | Optional HR system ID |
firstName |
given_name or firstName |
❌ | Optional |
lastName |
family_name or lastName |
❌ | Optional |
displayName |
name or displayName |
❌ | Auto-generated if missing |
department |
department |
❌ | Optional |
designation |
designation |
❌ | Optional |
phone |
phone_number or phone |
❌ | Optional |
manager |
manager |
❌ | Optional (extracted if available) |
secondEmail |
N/A | ❌ | Not available from Tanflow |
jobTitle |
employeeType |
❌ | Optional (maps employeeType to jobTitle) |
employeeNumber |
N/A | ❌ | Not available from Tanflow |
postalAddress |
address (structured) |
❌ | NOT currently extracted |
mobilePhone |
N/A | ❌ | Not available from Tanflow |
adGroups |
groups |
❌ | NOT currently extracted |
location |
address, city, state, country |
❌ | NOT currently extracted |
role |
Default: 'USER' | ✅ | Default role assigned |
isActive |
Default: true | ✅ | Auto-set to true |
lastLogin |
Current timestamp | ✅ | Auto-set on login |
Recommended Enhancements
1. Extract Additional Fields
Consider extracting these fields if available from Tanflow:
// Enhanced extraction (to be implemented)
const userData: SSOUserData = {
// ... existing fields ...
// Additional fields (already implemented)
manager: tanflowUserInfo.manager || undefined,
jobTitle: tanflowUserInfo.employeeType || undefined, // Map employeeType to jobTitle
postalAddress: tanflowUserInfo.address ? (typeof tanflowUserInfo.address === 'string' ? tanflowUserInfo.address : JSON.stringify(tanflowUserInfo.address)) : undefined,
mobilePhone: tanflowUserInfo.mobile_phone || tanflowUserInfo.mobilePhone || undefined,
adGroups: Array.isArray(tanflowUserInfo.groups) ? tanflowUserInfo.groups : undefined,
// Location object
location: {
city: tanflowUserInfo.city || undefined,
state: tanflowUserInfo.state || undefined,
country: tanflowUserInfo.country || undefined,
office: tanflowUserInfo.office_location || undefined,
timezone: tanflowUserInfo.zoneinfo || undefined,
},
};
2. Log Available Fields
Add logging to see what Tanflow actually returns:
logger.info('Tanflow userinfo response', {
availableFields: Object.keys(tanflowUserInfo),
hasEmail: !!tanflowUserInfo.email,
hasEmployeeId: !!(tanflowUserInfo.employeeId || tanflowUserInfo.employee_id),
hasDepartment: !!tanflowUserInfo.department,
hasManager: !!tanflowUserInfo.manager,
hasGroups: Array.isArray(tanflowUserInfo.groups),
groupsCount: Array.isArray(tanflowUserInfo.groups) ? tanflowUserInfo.groups.length : 0,
sampleData: {
sub: tanflowUserInfo.sub?.substring(0, 10) + '...',
email: tanflowUserInfo.email?.substring(0, 10) + '...',
name: tanflowUserInfo.name,
}
});
User Creation Flow
- Token Exchange → Get
access_tokenfrom Tanflow - Userinfo Call → Call
/protocol/openid-connect/userinfowithaccess_token - Extract Data → Map Tanflow fields to
SSOUserDatainterface - User Lookup → Check if user exists by
email - Create/Update → Create new user or update existing user
- Generate Tokens → Generate JWT access/refresh tokens
Testing Recommendations
-
Test with Real Tanflow Account
- Log actual userinfo response
- Document all available fields
- Verify field mappings
-
Handle Missing Fields
- Ensure graceful fallbacks
- Don't fail if optional fields are missing
- Log warnings for missing expected fields
-
Validate Required Fields
sub(oktaSub) - REQUIREDemailorpreferred_username- REQUIRED
Next Steps
- ✅ Current Implementation - Basic OIDC claims extraction
- 🔄 Enhancement - Extract additional custom claims (manager, groups, location)
- 🔄 Logging - Add detailed logging of Tanflow response
- 🔄 Testing - Test with real Tanflow account to see actual fields
- 🔄 Documentation - Update this doc with actual Tanflow response structure
Notes
- Tanflow uses Keycloak under the hood (based on URL structure)
- Keycloak supports custom user attributes that may be available
- Some fields may require specific realm/client configuration in Tanflow
- Contact Tanflow support to confirm available custom claims