In this article
June 19, 2026
June 19, 2026

SAML attribute mapping: A complete developer guide

How SAML attribute mapping works, how to configure it in Okta and Microsoft Entra ID, and how to map user roles, groups, and custom claims to your application.

Explore with AI
Open in ChatGPT
Open in Claude
Open in Perplexity

When a user logs in through SAML SSO, their identity provider doesn't just confirm who they are. It also ships a bundle of data about them: their name, email address, department, role, team, and whatever else the IdP has been configured to include. That bundle is called a SAML assertion, and the process of connecting the fields in that assertion to the fields your application expects is called attribute mapping.

Get it right and your app knows exactly who just logged in, what they're allowed to do, and which team to put them in. Get it wrong and users land on a login error, show up with the wrong permissions, or get provisioned into the wrong group. Attribute mapping problems are also notoriously hard to debug because the failures tend to be silent, or they surface as generic "access denied" messages that give no indication of where things went wrong.

This guide covers how SAML attribute mapping works, the most common mistakes, how to configure it correctly for Okta and Microsoft Entra ID, and how to test it without subjecting real users to the consequences of a bad configuration.

What SAML attributes actually are

A SAML assertion is an XML document that an identity provider signs and sends to your application after a user authenticates. The assertion has three main sections:

  • An authentication statement, which records when and how the user authenticated.
  • An attribute statement, which carries arbitrary key-value data about the user.
  • An authorization decision statement, which is optional and less commonly used.

The attribute statement is where all the user data lives. Here is a simplified example of what that section looks like in the raw XML:

  
<saml:AttributeStatement>
  <saml:Attribute Name="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress">
    <saml:AttributeValue>alice@example.com</saml:AttributeValue>
  </saml:Attribute>
  <saml:Attribute Name="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname">
    <saml:AttributeValue>Alice</saml:AttributeValue>
  </saml:Attribute>
  <saml:Attribute Name="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname">
    <saml:AttributeValue>Chen</saml:AttributeValue>
  </saml:Attribute>
  <saml:Attribute Name="department">
    <saml:AttributeValue>Engineering</saml:AttributeValue>
  </saml:Attribute>
  <saml:Attribute Name="role">
    <saml:AttributeValue>admin</saml:AttributeValue>
  </saml:Attribute>
</saml:AttributeStatement>
  

Each <saml:Attribute> element has a Name, which is the key used to identify it, and one or more <saml:AttributeValue> elements containing the actual data.

The Name value is whatever the IdP has been configured to use. There is no universal standard for how attributes are named. Okta, Entra ID, PingFederate, and Google Workspace all use different naming conventions by default, and every enterprise customer may have customized their IdP further on top of that. This is why attribute mapping is necessary: your application needs a consistent internal representation of user data regardless of how each IdP happens to format it.

The NameID: the one attribute that isn't an attribute

Before getting into the attribute statement, it's worth understanding the NameID separately because it behaves differently.

The NameID is the primary identifier for the user in a SAML assertion. It lives in the <saml:Subject> element rather than the <saml:AttributeStatement>, and it is always present. It looks like this:

  
<saml:Subject>
  <saml:NameID Format="urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress">
    alice@example.com
  </saml:NameID>
</saml:Subject>
  

The Format attribute describes what type of value the NameID contains. The most common formats are:

  • emailAddress: the NameID is an email address. This is the most commonly used format in enterprise SSO and makes user lookup straightforward.
  • persistent: an opaque, stable identifier that doesn't change across sessions, even if the user's email changes. Useful when you need a durable key for the user record.
  • transient: a temporary identifier that changes with each session. Rarely useful for most applications because you can't use it to match returning users.
  • unspecified: no format is declared. The value could be anything and your application has to treat it as an opaque string.

When your application receives a SAML response, you typically use the NameID as the lookup key for matching the assertion to a user in your own database. If the format changes, or if the IdP starts sending a different value in the NameID, your user matching logic will break. This is one of the most common causes of "user not found" errors during SSO logins.

How attribute names work across different IdPs

The biggest source of attribute mapping confusion is that there is no standard naming convention. The same piece of data, like a user's email address, can arrive under completely different attribute names depending on the IdP.

Here is how the most common IdPs name the standard attributes by default:

Attribute Okta Microsoft Entra ID Google Workspace
Email user.email or custom http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress email
First name user.firstName or custom http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname firstName
Last name user.lastName or custom http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname lastName
Display name user.displayName or custom http://schemas.microsoft.com/identity/claims/displayname displayName
Groups groups (requires group claim config) http://schemas.microsoft.com/ws/2008/06/identity/claims/groups groups (requires claim config)

The Microsoft schema URIs are particularly verbose because they follow the WS-Federation claim type conventions inherited from Active Directory. They are fully valid SAML attribute names, but they look jarring if you're used to working with simpler identifiers.

Your application needs to be ready to accept different attribute names from different customers' IdPs and map them to a consistent internal model. The way this is typically handled is by letting each SSO connection have its own attribute mapping configuration, which tells your application "for this connection, the user's email is in the attribute named X, their role is in the attribute named Y."

Configuring attribute mapping in Okta

When you set up a SAML application in Okta, the attribute statements are configured in the SAML settings for that application. Here is how to set it up for a typical B2B SaaS application.

Step 1: Navigate to SAML settings

In the Okta admin console, go to Applications and open your application. Under the "General" tab, find the SAML settings section and click "Edit."

Step 2: Add attribute statements

Scroll down to the "Attribute Statements" section. Each row maps an attribute name (what your application will receive) to an Okta user profile field (where Okta gets the value from).

A typical configuration looks like this:

Name Name format Value
email Unspecified user.email
firstName Unspecified user.firstName
lastName Unspecified user.lastName
department Unspecified user.department

The "Name" column is what will appear in the <saml:Attribute Name="..."> element in the SAML assertion your application receives. The "Value" column is an Okta expression that pulls from the user's Okta profile.

Step 3: Add group attribute statements

If your application uses SAML attributes to assign roles or groups, you also need to configure group attribute statements. These are separate from regular attribute statements in the Okta UI.

In the "Group Attribute Statements" section, add a row like this:

Name Name format Filter Value
groups Unspecified Matches regex .*

The filter controls which Okta groups are included in the assertion. Using .* sends all groups the user belongs to. In practice, you will want to scope this more narrowly, for example using a prefix like myapp- so you only send the groups that are relevant to your application rather than every group in the customer's directory.

Step 4: Verify the assertion

After saving, use the Okta SAML debugger or a browser extension like SAML Tracer to capture a live assertion and verify the attributes are present and formatted correctly. Decode the Base64-encoded SAMLResponse and confirm the attribute names and values match what your application expects.

Configuring attribute mapping in Microsoft Entra ID

Entra ID (formerly Azure Active Directory) has a slightly different mental model for attribute mapping. It calls the attributes "claims" and ships a default set of claims for every SAML application. You configure them in the "Attributes and Claims" section of the enterprise application.

Step 1: Open the application in Entra ID

In the Entra ID admin center, go to Enterprise Applications, open your application, and select "Single sign-on." Under "Attributes and Claims," click "Edit."

Step 2: Review and modify the default claims

Entra ID sends a default set of claims that covers the most common attributes. The defaults look like this:

Claim name Value
http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress user.mail
http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname user.givenname
http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname user.surname
http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name user.userprincipalname

If your application expects simpler attribute names like email instead of the full URI, you can add new claims with shorter names. Click "Add new claim," enter the name your application expects, and map it to the appropriate user attribute.

Step 3: Add group claims

Group claims in Entra ID are configured separately. At the top of the "Attributes and Claims" panel, click "Add a group claim."

You can choose which groups to include:

  • "Security groups" sends all security groups the user belongs to.
  • "Groups assigned to the application" sends only the groups explicitly assigned to the enterprise application. This is usually the right choice because it keeps the assertion size manageable and avoids leaking your customer's internal group structure.

For each group, Entra ID can send either the group's display name or its object ID (a GUID). Sending the display name is more readable but brittle if group names change. Sending the object ID is stable but requires your application to map GUIDs to meaningful roles, which means you need to know the GUIDs in advance for each customer.

Step 4: Handle the "bloated assertion" problem

One thing to watch out for with Entra ID: users who belong to many security groups can generate enormous SAML assertions. A user in 100 groups will produce an assertion that contains 100 <saml:AttributeValue> elements under the groups claim. This can exceed the maximum size limits for HTTP headers or POST bodies, causing silent failures that are very hard to trace.

The solution is to use the "Groups assigned to the application" scope so that only a small, controlled set of groups is included, or to switch from group claims to app role claims, which are scoped to the specific application and never include unrelated directory groups.

Mapping attributes in your application

Once you understand what the IdP is sending, you need to tell your application how to interpret it. The approach depends on how you have structured your SSO integration.

Basic attribute extraction

If you are handling SAML responses directly, you will parse the XML and extract attributes by name. Here is a simplified Node.js example:

  
function extractUserFromAssertion(assertion) {
  const attributes = assertion.attributes;

  return {
    email: attributes['email']
        || attributes['http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress']
        || assertion.nameID,
    firstName: attributes['firstName']
        || attributes['http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname'],
    lastName: attributes['lastName']
        || attributes['http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname'],
    department: attributes['department'],
    groups: [].concat(attributes['groups'] || []),
  };
}
  

The fallback chain in the email field shows the right pattern: try the simple name first, then the verbose URI form, then fall back to the NameID. This makes your integration tolerant of the naming differences across IdPs without requiring you to configure each customer's mapping manually.

Per-organization mapping configuration

For a real B2B application with multiple enterprise customers, you will need to store mapping configuration per SSO connection rather than hardcoding it. A minimal configuration object might look like this:

  
const connectionConfig = {
  organizationId: 'org_123',
  idpType: 'okta',
  attributeMapping: {
    email: 'user.email',
    firstName: 'firstName',
    lastName: 'lastName',
    role: 'app_role',
    groups: 'groups',
  },
};
  

When a SAML response arrives, you look up the connection configuration and use it to extract the right attributes:

  
function extractUserAttributes(assertion, mappingConfig) {
  const result = {};

  for (const [internalKey, samlAttributeName] of Object.entries(mappingConfig)) {
    const value = assertion.attributes[samlAttributeName];
    if (value !== undefined) {
      result[internalKey] = Array.isArray(value) ? value : [value];
    }
  }

  return result;
}
  

This approach lets you support any IdP configuration without code changes. When a new enterprise customer has a non-standard attribute name, you update their connection configuration rather than shipping a new version of your application.

Mapping roles and permissions

Role and permission mapping is where attribute configuration gets most interesting, and most consequential. A misconfigured role mapping can give users too much access or too little, neither of which is acceptable in a production system.

There are two common patterns:

  • Direct role mapping: the IdP sends a role attribute whose value directly corresponds to a role in your application, such as admin, editor, or viewer. This is simple to implement but requires the customer's IT team to keep the IdP values synchronized with your application's role names. Any change to your role names requires a coordination step with every customer.
  • Group-to-role mapping: the IdP sends a groups attribute containing the names or IDs of the groups the user belongs to, and your application maps those groups to internal roles. This requires more configuration up front but is more robust because group membership is managed entirely in the IdP and your application just maps groups to roles.

A group-to-role mapping configuration might look like this:

  
const groupRoleMap = {
  'myapp-admins': 'admin',
  'myapp-editors': 'editor',
  'myapp-viewers': 'viewer',
};

function resolveRole(groups, groupRoleMap) {
  for (const group of groups) {
    if (groupRoleMap[group]) {
      return groupRoleMap[group];
    }
  }
  return 'viewer'; // default role if no group matches
}
  

The default role is important. If a user's groups don't match any mapping, you need to decide whether to assign a safe default role or to deny access entirely. For most applications, failing safe means denying access and showing a clear error message rather than granting a default level of access that might be too permissive.

Common attribute mapping problems and how to fix them

Missing attribute error

The most common problem: your application expects an attribute that isn't present in the assertion.

Cause: the IdP is not configured to send the attribute, or it is sending it under a different name.

Fix: check the raw assertion using SAML Tracer or a similar tool. Decode the Base64-encoded SAMLResponse and look at what attributes are actually present. If the attribute is missing entirely, the customer's IT admin needs to add it in the IdP's attribute statement configuration. If it's present but under a different name, update your mapping configuration to use the name the IdP is actually sending.

Wrong attribute value format

The attribute is present but its value isn't in the format your application expects.

Cause: different IdPs format the same data differently. Dates, booleans, and multi-value attributes are the most common offenders.

Fix: add transformation logic in your attribute extraction layer to normalize values to the expected format. For example, if one IdP sends true as a string and another sends 1, your extractor should normalize both to a boolean.

Group attributes exceeding HTTP limits

Users who belong to many groups can generate assertions that are too large for your web server to handle.

Cause: the IdP is including all directory groups rather than a filtered subset.

Fix: configure the IdP to filter group claims to only include groups that are relevant to your application. In Okta, use the group attribute statement filter. In Entra ID, switch from "All groups" to "Groups assigned to the application" or use app roles instead.

NameID format mismatch

The user can't be matched to an existing account on subsequent logins.

Cause: the IdP is sending a transient NameID format, or the NameID value is changing between sessions (which can happen if the IdP is configured to use the user's email and the email changes).

Fix: configure the IdP to use a persistent or emailAddress NameID format. If you are storing the NameID as the primary key for user lookup, prefer persistent because it won't change even if the user's email does.

Certificate-related signature failures

The assertion is present and well-formed but your application rejects it with a signature validation error.

Cause: the X.509 certificate your application has configured for the IdP doesn't match the certificate the IdP is currently using to sign assertions. This often happens after the IdP rotates its signing certificate.

Fix: fetch the current IdP metadata and update the certificate in your connection configuration. Most IdPs publish metadata at a well-known URL that you can poll to detect certificate changes before they cause an outage. See our guide on SAML certificates for a full walkthrough of certificate lifecycle management.

Testing attribute mapping before going live

Never test attribute mapping with real production users. Use these approaches instead.

  • Capture a live assertion in staging: configure your SAML connection to point to a staging environment, perform a test login, and capture the SAML response using SAML Tracer or your browser's developer tools. Decode the response and inspect the attributes manually before building any parsing logic around them.
  • Use IdP developer tenants: both Okta and Entra ID offer free developer accounts that you can use to simulate any IdP configuration without touching a customer's production environment. Set up a test application, configure the attribute statements, and perform test logins to verify the output.
  • Log the full attribute map on first login: in development and staging, log every attribute present in the SAML assertion when a new user logs in. This gives you a complete picture of what the IdP is actually sending, which is often different from what the customer's IT team says they've configured.
  • Test multi-value attributes: don't assume every attribute arrives as a single value. Group attributes in particular often arrive as arrays. Make sure your extraction logic handles both "admin" and ["admin"] without breaking.
  • Test the role boundary cases: explicitly test what happens when a user has no matching group, multiple matching groups, or a group that maps to the highest-privilege role. Make sure the behavior is what you intended in each case.

How WorkOS handles attribute mapping

If you're using WorkOS for SSO, attribute mapping is handled through a combination of automatic normalization and a configurable attribute system in the WorkOS dashboard.

WorkOS normalizes a standard set of attributes from every supported IdP into a consistent SSO Profile object. These standard attributes (idp_id, email, first_name, and last_name) are always present and always use the same field names regardless of which IdP the user's company uses, so your application code doesn't need to handle naming differences between Okta, Entra ID, PingFederate, and others.

Beyond the standard set, WorkOS supports two additional tiers. Predefined attributes cover more detailed user data (job_title, department_name, manager_email, employee_type, and others) which you can opt into from the Identity Provider Attributes page in the WorkOS dashboard. Once enabled, your customers' IT contacts are prompted to map these fields when configuring their SSO connection in the Admin Portal. For anything not covered by predefined attributes, you can define your own custom attributes in the dashboard, which then appear as mapping fields in the Admin Portal for your customers to configure.

Attribute mappings can be edited at any time, either by you in the WorkOS dashboard or by the customer's IT contact in the Admin Portal, without requiring a code change. Changes take effect on the user's next sign-in.

You can try it out by signing up for a free WorkOS account and following the SSO quickstart.

Key takeaways

SAML attribute mapping is often treated as a minor configuration step, but it sits at the center of how your application understands who a user is and what they're allowed to do. A few things to keep in mind:

  • The NameID and the attribute statement serve different purposes. Use the NameID as your stable user identifier and the attribute statement for everything else.
  • Attribute names are not standardized across IdPs. Design your integration to accept different names for the same data rather than assuming every customer will use the same naming convention.
  • Group and role mapping requires upfront agreement with your customer's IT team about which groups map to which roles, and a safe default for users who don't match any configured group.
  • Test with a decoded assertion before shipping anything. The raw XML tells you exactly what the IdP is actually sending, which is the only reliable source of truth during setup.
  • Keep assertion sizes manageable by filtering group claims to the subset your application actually needs.