Skip to content

Attribute mapping of NameID in Cognito User Pool SAML Federation

3

I have AWS Cognito User Pool federated with external SAML IdP (Salesforce). The SSO flow work as expected, and federated users are automatically created with username set to %IdPName%_%NameID% (as documented here) and attributes set to proper SAML values, as defined in "Attribute mapping".

What I need now is to also map the original value of NameID from SAML token to a user attribute (e.g. preferred_username). According to the same documentation, it should be possible:

Amazon Cognito derives the username attribute in a federated user's profile from specific claims that your federated IdP passes, as shown in the following table. Amazon Cognito prepends this attribute value with the name of your IdP, for example MyOIDCIdP_[sub]. When you want your federated users to have an attribute that exactly matches an attribute in your external user directory, map that attribute to a Amazon Cognito sign-in attribute like preferred_username.

I added a new attribute mapping for preferred_username, but I can't make it being populated with NameID from SAML subject. I've tried "NameID", "nameID", "NameIdentifier" and even URI format "urn:oasis:names:tc:SAML:2.0:nameid-format:persistent", but it doesn't work.

For the reference, here is how the NameID element looks like in our SAML tokens:

<saml2:Subject>
    <saml2:NameID  Format="urn:oasis:names:tc:SAML:2.0:nameid-format:persistent">003FS00001663th</saml2:NameID>
...
</saml2:Subject>
2 Answers
0

Greeting

Hi there, dzazuliak!

Thank you for such a detailed and well-explained question. It's great to see you're diving deep into the nuances of attribute mapping with SAML federation in Amazon Cognito User Pools. Let's unpack your issue and provide a comprehensive solution that ensures everything works as intended. 😊


Clarifying the Issue

You're working with AWS Cognito User Pools federated with Salesforce as an external SAML IdP. Your SSO flow functions as expected, and federated users are created with usernames in the format %IdPName%_%NameID%. However, you're encountering a challenge in mapping the NameID value from the SAML token (located in the <saml2:Subject> block) to a specific Cognito user attribute, such as preferred_username.

You've tried several configurations for the attribute mapping—NameID, nameID, NameIdentifier, and even the URI format urn:oasis:names:tc:SAML:2.0:nameid-format:persistent—but none have succeeded in populating the preferred_username attribute.

Based on the <saml2:NameID> structure in your SAML token, you’ve correctly identified the format as urn:oasis:names:tc:SAML:2.0:nameid-format:persistent. Let’s work through a solution to map this correctly and troubleshoot potential roadblocks.


Why This Matters

Mapping the NameID to a user attribute like preferred_username is vital for ensuring the integrity of identity data across systems. It allows seamless integration with downstream systems, preserves user identity, and can simplify user management. For instance, retaining this attribute might be critical for audit trails, role assignments, or personalized application experiences.

Successfully implementing this mapping ensures that your federated users' profiles in Cognito match the identity information passed from your external SAML IdP, maintaining consistency across your identity ecosystem.


Key Terms

  • SAML (Security Assertion Markup Language): A protocol for exchanging authentication and authorization data between an IdP and a service provider.
  • NameID: A core SAML attribute uniquely identifying the subject of the assertion.
  • Attribute Mapping: A Cognito mechanism to link IdP attributes to Cognito user attributes.
  • preferred_username: A standard Cognito user attribute used to store a user's preferred display or login name.
  • IdP (Identity Provider): A service like Salesforce that authenticates users and issues SAML assertions.

The Solution (Our Recipe)

Steps at a Glance:

  1. Verify the NameID in the SAML response using a SAML assertion decoder.
  2. Update the attribute mapping in your Cognito User Pool to correctly reference the NameID attribute.
  3. Validate the updated mapping by testing the SSO flow.
  4. Debug and troubleshoot any remaining issues, focusing on logs and attribute alignment.
  5. Address edge cases.

Step-by-Step Guide:

  1. Verify the SAML Response:

    • Decode the SAML response to inspect the exact structure and value of the NameID. Use tools like SAML Tracer or SAMLTool.
    • Example NameID from your SAML response:
      <saml2:NameID Format="urn:oasis:names:tc:SAML:2.0:nameid-format:persistent">003FS00001663th</saml2:NameID>

    Confirm that:

    • The NameID element is present and uses the correct format.
    • The Format value matches urn:oasis:names:tc:SAML:2.0:nameid-format:persistent.

  1. Update the Attribute Mapping:

    • In the Cognito console, go to Federation > Attribute Mapping.

    • Map the NameID from the SAML assertion to preferred_username in Cognito. Use the exact attribute name or URI format:

      • Key: urn:oasis:names:tc:SAML:2.0:nameid-format:persistent
      • Value: preferred_username

      For fine-grained control, use a JSON-based approach:

      {
        "AttributeMapping": {
          "urn:oasis:names:tc:SAML:2.0:nameid-format:persistent": "preferred_username"
        }
      }

    If you're configuring this via the AWS CLI, the attribute mapping command may look like this:

    aws cognito-idp update-user-pool \
        --user-pool-id us-east-1_examplePoolId \
        --user-pool-name my_user_pool \
        --attribute-mapping '{"urn:oasis:names:tc:SAML:2.0:nameid-format:persistent": "preferred_username"}'

  1. Test the Federation Setup:

    • Perform a test login using your SSO flow.
    • Inspect the resulting Cognito user to ensure the preferred_username attribute is populated with the NameID.

    Use the AWS CLI to verify the user's attributes:

    aws cognito-idp admin-get-user \
        --user-pool-id us-east-1_examplePoolId \
        --username %IdPName%_003FS00001663th

  1. Debug and Troubleshoot:
    • Check CloudTrail logs for the SAML assertion to confirm that the NameID is passed as expected.
    • Ensure that no other conflicting mappings exist for preferred_username.
    • Validate IdP configurations in Salesforce:
      • Ensure the correct NameID format is being sent in the SAML assertion.
      • If necessary, enable detailed logging in Salesforce to verify the exact attribute mappings.
    • Use Amazon Cognito logs to troubleshoot attribute mapping issues.

  1. Address Edge Cases:
    • If NameID uses a different format (e.g., transient), update your mappings accordingly.
    • In scenarios with multiple SAML assertions, ensure the appropriate one is being mapped.

Closing Thoughts

Mapping the NameID to a Cognito user attribute like preferred_username ensures a seamless and reliable identity integration between your SAML IdP and Cognito. By verifying your SAML response, updating attribute mappings, and testing thoroughly, you can achieve the desired results.

For further assistance, here are some helpful resources:

Let us know how this works out for you! 😊🚀


Farewell

I hope this enhanced solution provides all the clarity and actionable steps you need to resolve the issue, dzazuliak. If you have any additional questions or run into roadblocks, feel free to reach out—I'm here to help! Good luck with your SSO integration! 🎉


Cheers,

Aaron 😊

answered 2 years ago

  • Hi Aarron, the solution you have here doesn't look to be working for me (also have the exact same case as the OP). Using the NameID format as the key still doesn't get it to work. I verified by logging the event in a preSignup lambda trigger, and preferred_username remains empty. Subject>NameId aren't part of the AttributeSatements, so on one hand it makes sense if we can't map them automatically, but we really need a way to capture the NameId in it's original form, as by the time it's in the username, cognito has already formated it and lowercased if if the pool is case insensitive.

    So we are still looking for an answer to the original question.

0

Greeting

Hi Dzazuliak,

Thank you for the detailed follow-up and for your patience as we work through this together. It's clear you're diving into a challenging but important area of Cognito's functionality, and we're here to help you navigate it. 😊


Clarifying the Situation

From your explanation, it's evident that you're trying to do something with Cognito that it’s not fully designed to handle out of the box. Cognito excels at attribute mappings from the <AttributeStatement> section of SAML assertions, but the NameID, which resides in the <Subject> element, falls outside its standard mapping capabilities. As a result, some custom work is necessary to achieve your desired outcome.

This is not unusual in scenarios like yours, where integrations with external IdPs (like Salesforce) require additional steps to ensure the integrity and usability of identity data. Let’s build on the groundwork you've laid and outline how you can extract and map the original NameID to a user attribute, such as preferred_username, using a custom Lambda trigger.


Solution: Custom Extraction Using Pre-Authentication Lambda Trigger

To work around Cognito's limitations, you can leverage a pre-authentication Lambda trigger. This allows you to intercept the authentication flow, decode the SAML assertion, extract the <NameID> from the <Subject> element, and map it manually to the preferred_username attribute. Here's how you can implement this:

  1. Set Up a Pre-Authentication Lambda Trigger
    Attach a Lambda function to your Cognito User Pool’s pre-authentication trigger. This gives you programmatic control over the authentication flow and user attributes.

  2. Extract the <NameID>
    Use the Lambda function to decode the SAML token, identify the <NameID> in the <Subject> block, and extract its value. This step is necessary since Cognito doesn't natively parse the <Subject> element.

  3. Map the Value
    Once the <NameID> is extracted, assign it to the preferred_username attribute in Cognito. You can either modify the event object in the Lambda function or use the AWS SDK to update the user attributes.

Here’s an example of how a Lambda function could look in Python:

import boto3
import xml.etree.ElementTree as ET

def lambda_handler(event, context):
    # Extract the SAML assertion (example structure, adjust if needed)
    saml_assertion = event['request']['userAttributes']['cognito:identity_provider_claims']
    
    # Parse the SAML XML
    root = ET.fromstring(saml_assertion)
    ns = {'saml2': 'urn:oasis:names:tc:SAML:2.0:assertion'}
    
    # Extract the NameID
    name_id = root.find('.//saml2:NameID', ns).text
    
    # Update the preferred_username attribute
    event['request']['userAttributes']['preferred_username'] = name_id
    
    # Optionally save back to Cognito
    cognito = boto3.client('cognito-idp')
    cognito.admin_update_user_attributes(
        UserPoolId=event['userPoolId'],
        Username=event['userName'],
        UserAttributes=[
            {'Name': 'preferred_username', 'Value': name_id}
        ]
    )
    
    return event
  1. Test and Validate
    Run a test SSO flow to verify that the preferred_username attribute is populated with the <NameID> value in its original form. Check the user profile in Cognito to confirm the changes.

Why This Approach is Necessary

Since Cognito is not built to automatically map attributes from the <Subject> element, the custom Lambda function bridges that gap. This approach ensures the integrity of your identity data while maintaining control over how the attributes are handled. While it's a bit of extra work, it provides the flexibility to meet your specific requirements.


Farewell

I hope this solution helps clarify the path forward and provides a clear framework for achieving your goal. If you have any questions or run into issues implementing this, don’t hesitate to reach out—we’re here to help you every step of the way. Best of luck with your integration, Dzazuliak! 🚀😊

Cheers,
Aaron 😊

answered 2 years ago

You are not logged in. Log in to post an answer.

A good answer clearly answers the question and provides constructive feedback and encourages professional growth in the question asker.