Skip to contentSkip to navigationSkip to topbar
On this page

Pass Custom Azure AD Attributes as Twilio Flex SAML Claims


Azure AD extension properties can only be used as Enterprise Application SAML claims through a Claims Mapping Policy(link takes you to an external page) created using the Graph API or Powershell. The Azure AD portal interface does not support adding extension properties as claims. This guide uses the Graph API to walk you through the process of creating an Azure AD extension property, a claims mapping policy, and passing the property as a custom attribute for your Flex users.


Open Microsoft Graph Explorer

open-microsoft-graph-explorer page anchor
  1. Open and login to your Azure portal(link takes you to an external page).

  2. In a separate tab, open the Microsoft Graph API Explorer(link takes you to an external page), click "Sign in to Graph Explorer", and select your Azure administrator account to use for Graph Explorer.

    graph-explorer-signin.
  3. Make sure to consent to the following permissions requirements(link takes you to an external page):

    msgraph-consent.

We will need to retrieve a couple IDs for use in the next steps: your Service principal entity ID and your Application entity ID. Click on the respective tab for detailed instructions.

Service Principal Entity IDApplication Entity ID

Run the following Graph Explorer query to retrieve your Service Principal Entity ID. Make sure to:

  • Replace with a portion of your Enterprise app name on the URL field. For example, if your app name is "Twilio Flex", you can enter "displayname:flex" as the $search query parameter value.
  • Click on the Request headers tab to add the key-value pair.

Example Request

1
HTTP method: GET
2
API version: v1.0
3
Endpoint URL: https://graph.microsoft.com/v1.0/servicePrincipals?$search="displayName:<Your_App_Name>"
4
Request header key: ConsistencyLevel
5
Request header value: eventual
  • Click Run query and in the JSON response, look for the array object whose "appDisplayName" value matches your Enterprise Application name.
  • Copy the "id" value and paste it somewhere you can reference it.

Example Response

1
{
2
"@odata.context": "https://graph.microsoft.com/v1.0/$metadata#servicePrincipals",
3
"value": [
4
{
5
"id": "cdxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
6
"deletedDateTime": null,
7
"accountEnabled": true,
8
"alternativeNames": [],
9
"appDisplayName": "Twilio Flex testing",
10
"appDescription": null,
11
"appId": "xxx",
12
"applicationTemplateId": "xxx",
13
"appOwnerOrganizationId": "xxx",
14
"appRoleAssignmentRequired": true,
15
"createdDateTime": "2021-11-24T18:17:10Z",
16
"description": null,
17
"disabledByMicrosoftStatus": null,
18
"displayName": "Twilio Flex testing",
19
"homepage": "https://account.activedirectory.windowsazure.com:444/applications/default.aspx?metadata=customappsso|ISV9.1|primary|z",
20
"loginUrl": null,
21
"logoutUrl": null,
22
"notes": null,
23
"notificationEmailAddresses": [
24
"user@userdomain.onmicrosoft.com"
25
],
26
"preferredSingleSignOnMode": "saml",
27
"preferredTokenSigningKeyThumbprint": "xxxx",
28
"replyUrls": [
29
"https://iam.twilio.com/v1/Accounts/ACXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/saml2/"
30
],
31
"servicePrincipalNames": [
32
"https://iam.twilio.com/v1/Accounts/ACXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/saml2/metadata",
33
"xxx"
34
],
35
"servicePrincipalType": "Application",
36
"signInAudience": "AzureADMyOrg",
37
"tags": [
38
"WindowsAzureActiveDirectoryCustomSingleSignOnApplication",
39
"WindowsAzureActiveDirectoryIntegratedApp"
40
],
41
"tokenEncryptionKeyId": null,
42
"resourceSpecificApplicationPermissions": [],
43
"verifiedPublisher": {
44
"displayName": null,
45
"verifiedPublisherId": null,
46
"addedDateTime": null
47
},
48
"addIns": [],
49
"appRoles": [
50
{
51
"allowedMemberTypes": [
52
"User"
53
],
54
"description": "admin",
55
"displayName": "Admin",
56
"id": "yyy",
57
"isEnabled": true,
58
"origin": "Application",
59
"value": "admin"
60
},
61
{
62
"allowedMemberTypes": [
63
"User"
64
],
65
"description": "supervisor",
66
"displayName": "Supervisor",
67
"id": "zzz",
68
"isEnabled": true,
69
"origin": "Application",
70
"value": "supervisor"
71
},
72
{
73
"allowedMemberTypes": [
74
"User"
75
],
76
"description": "agent",
77
"displayName": "Agent",
78
"id": "aaa",
79
"isEnabled": true,
80
"origin": "Application",
81
"value": "agent"
82
},
83
{
84
"allowedMemberTypes": [
85
"User"
86
],
87
"description": "msiam_access",
88
"displayName": "msiam_access",
89
"id": "bbb",
90
"isEnabled": true,
91
"origin": "Application",
92
"value": null
93
}
94
],
95
"info": {
96
"logoUrl": null,
97
"marketingUrl": null,
98
"privacyStatementUrl": null,
99
"supportUrl": null,
100
"termsOfServiceUrl": null
101
},
102
"keyCredentials": [
103
{
104
"customKeyIdentifier": "xxx",
105
"displayName": "CN=Microsoft Azure Federated SSO Certificate",
106
"endDateTime": "2024-11-24T18:24:49Z",
107
"key": null,
108
"keyId": "xxx",
109
"startDateTime": "2021-11-24T18:24:49Z",
110
"type": "AsymmetricX509Cert",
111
"usage": "Verify"
112
},
113
{
114
"customKeyIdentifier": "xxx",
115
"displayName": "CN=Microsoft Azure Federated SSO Certificate",
116
"endDateTime": "2024-11-24T18:24:49Z",
117
"key": null,
118
"keyId": "xxx",
119
"startDateTime": "2021-11-24T18:24:49Z",
120
"type": "AsymmetricX509Cert",
121
"usage": "Sign"
122
}
123
],
124
"oauth2PermissionScopes": [
125
{
126
"adminConsentDescription": "Allow the application to access Twilio Flex testing on behalf of the signed-in user.",
127
"adminConsentDisplayName": "Access Twilio Flex testing",
128
"id": "xxx",
129
"isEnabled": true,
130
"type": "User",
131
"userConsentDescription": "Allow the application to access Twilio Flex testing on your behalf.",
132
"userConsentDisplayName": "Access Twilio Flex testing",
133
"value": "user_impersonation"
134
}
135
],
136
"passwordCredentials": [
137
{
138
"customKeyIdentifier": "xxx",
139
"displayName": "CN=Microsoft Azure Federated SSO Certificate",
140
"endDateTime": "2024-11-24T18:24:49Z",
141
"hint": null,
142
"keyId": "xxx",
143
"secretText": null,
144
"startDateTime": "2021-11-24T18:24:49Z"
145
}
146
],
147
"samlSingleSignOnSettings": {
148
"relayState": ""
149
}
150
}
151
]
152
}

Create Azure AD Extension Property

create-azure-ad-extension-property page anchor

In this section, we're going to create an extended (custom) property for the Enterprise Application that we can then populate for our target Flex users.

Run the following Graph Explorer query. Make sure to:

  • In the URL field, replace <Application_Entity_ID> with the application entity ID you noted earlier.
  • Click on the Request body tab to add the JSON in the Request body field. Replace <Property_Name> with the name you want to use for the extension.

Example Request

example-request-3 page anchor
1
HTTP method: POST
2
API version: v1.0
3
Endpoint URL: https://graph.microsoft.com/v1.0/applications/<Application_Entity_ID>/extensionProperties
4
Request body:
5
{
6
"name": "<Property_Name>",
7
"dataType": "String",
8
"targetObjects": ["User"]
9
}
10

Click Run query and in the JSON response, copy and paste the "name" value of the new extension property for reference in later steps. It will be in the format "extension_<App_ID>_<Property_Name>".

1
{
2
"@odata.context": "https://graph.microsoft.com/v1.0/$metadata#applications('xxx')/extensionProperties/$entity",
3
"id": "xxx",
4
"deletedDateTime": null,
5
"appDisplayName": "Twilio Flex testing",
6
"dataType": "String",
7
"isSyncedFromOnPremises": false,
8
"name": "extension_xxx_language",
9
"targetObjects": [
10
"User"
11
]
12
}

Populate Extension Property on Azure AD User

populate-extension-property-on-azure-ad-user page anchor

Now that we have an extension property created, let's add it to a user.

Run the following Graph Explorer query. Make sure to:

  • In the URL field, you can pass either the user ID or the user principal name (UPN). Replace <ID \| UserPrincipalName> with either the object ID or the UPN of the Azure AD user you want to update.

  • Click on the Request body tab to add the JSON in the Request body field.

    • Replace <extension_<App_ID>_<Property_Name> with the name of the extension property created in the previous step.
    • Replace "My Custom Value" with the value you want to assign to this property.
1
HTTP method: PATCH
2
API version: v1.0
3
Endpoint URL: https://graph.microsoft.com/v1.0/users/<ID | UserPrincipalName>
4
Request body:
5
{
6
"extension_<App_ID>_<Property_Name>": "My Custom Value"
7
}
8
9

Click Run query.

204 {}

To confirm the user was updated successfully, run the following query.

1
HTTP method: GET
2
API version: beta
3
Endpoint URL: https://graph.microsoft.com/beta/users/<ID | UserPrincipalName>
4

In the JSON response, you should see the new extension property and assigned value in the user's properties.

1
{
2
"@odata.context": "https://graph.microsoft.com/beta/$metadata#users/$entity",
3
"id": "xxxx",
4
"deletedDateTime": null,
5
"accountEnabled": true,
6
"ageGroup": null,
7
"businessPhones": [],
8
"city": null,
9
"createdDateTime": "2021-11-19T06:55:28Z",
10
"creationType": null,
11
"companyName": null,
12
"consentProvidedForMinor": null,
13
"country": null,
14
"department": null,
15
"displayName": "Test User",
16
"employeeId": null,
17
"employeeHireDate": null,
18
"employeeType": null,
19
"faxNumber": null,
20
"givenName": "Test",
21
"imAddresses": [],
22
"infoCatalogs": [],
23
"isManagementRestricted": null,
24
"isResourceAccount": null,
25
"jobTitle": null,
26
"legalAgeGroupClassification": null,
27
"mail": null,
28
"mailNickname": "testuser",
29
"mobilePhone": null,
30
"onPremisesDistinguishedName": null,
31
"officeLocation": null,
32
"onPremisesDomainName": null,
33
"onPremisesImmutableId": null,
34
"onPremisesLastSyncDateTime": null,
35
"onPremisesSecurityIdentifier": null,
36
"onPremisesSamAccountName": null,
37
"onPremisesSyncEnabled": null,
38
"onPremisesUserPrincipalName": null,
39
"otherMails": [],
40
"passwordPolicies": null,
41
"postalCode": null,
42
"preferredDataLocation": null,
43
"preferredLanguage": null,
44
"proxyAddresses": [],
45
"refreshTokensValidFromDateTime": "2021-11-24T19:10:02Z",
46
"showInAddressList": null,
47
"signInSessionsValidFromDateTime": "2021-11-24T19:10:02Z",
48
"state": null,
49
"streetAddress": null,
50
"surname": "User",
51
"usageLocation": null,
52
"userPrincipalName": "testuser@admintwilio.onmicrosoft.com",
53
"externalUserState": null,
54
"externalUserStateChangeDateTime": null,
55
"userType": "Member",
56
"extension_xxx_language": "Russian",
57
"employeeOrgData": null,
58
"passwordProfile": null,
59
"assignedLicenses": [],
60
"assignedPlans": [],
61
"deviceKeys": [],
62
"identities": [
63
{
64
"signInType": "userPrincipalName",
65
"issuer": "admintwilio.onmicrosoft.com",
66
"issuerAssignedId": "testuser@admintwilio.onmicrosoft.com"
67
}
68
],
69
"onPremisesExtensionAttributes": {
70
"extensionAttribute1": null,
71
"extensionAttribute2": null,
72
"extensionAttribute3": null,
73
"extensionAttribute4": null,
74
"extensionAttribute5": null,
75
"extensionAttribute6": null,
76
"extensionAttribute7": null,
77
"extensionAttribute8": null,
78
"extensionAttribute9": null,
79
"extensionAttribute10": null,
80
"extensionAttribute11": null,
81
"extensionAttribute12": null,
82
"extensionAttribute13": null,
83
"extensionAttribute14": null,
84
"extensionAttribute15": null
85
},
86
"onPremisesProvisioningErrors": [],
87
"provisionedPlans": []
88
}
89
(warning)

Warning

If you don't change the API version to beta, you'll only get a handful of user properties instead of the full list.


Create a Claims Mapping Policy

create-a-claims-mapping-policy page anchor

We can now create our claims mapping policy. For an example claim definition and a walkthrough of its key configuration properties, see "Example Claims Policy Definition".

Run the following Graph Explorer query. Make sure to:

  • Click on the Request body tab and enter the JSON in the Request body field. Make the following changes:

1
HTTP method: POST
2
API version: v1.0
3
Endpoint URL: https://graph.microsoft.com/v1.0/policies/claimsMappingPolicies
4
Request body:
5
{
6
"definition": [
7
"{\"ClaimsMappingPolicy\":{\"Version\":1,\"IncludeBasicClaimSet\":\"true\",\"ClaimsSchema\":[{\"Source\":\"user\",\"ID\":\"userprincipalname\",\"SamlClaimType\":\"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier\"},{\"Source\":\"user\",\"ID\":\"displayname\",\"SamlClaimType\":\"full_name\"},{\"Source\":\"user\",\"ID\":\"assignedroles\",\"SamlClaimType\":\"roles\"},{\"Source\":\"user\",\"ID\":\"mail\",\"SamlClaimType\":\"email\"},{\"Source\":\"user\",\"ExtensionID\":\"extension_<App_ID>_<Property_Name>\",\"SamlClaimType\":\"<Claim_Attribute_Name>\"}]}}"
8
],
9
"displayName": "<Claim_Policy_Name>",
10
"isOrganizationDefault": false
11
}

Click Run query. In the JSON response, copy the claims mapping policy "id" value and paste it somewhere you can reference it.

1
201 Created
2
{
3
"@odata.context": "https://graph.microsoft.com/v1.0/$metadata#policies/claimsMappingPolicies/$entity",
4
"id": "xxx",
5
"deletedDateTime": null,
6
"definition": [
7
"{\"ClaimsMappingPolicy\":{\"Version\":1,\"IncludeBasicClaimSet\":\"true\",\"ClaimsSchema\":[{\"Source\":\"user\",\"ID\":\"userprincipalname\",\"SamlClaimType\":\"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier\"},{\"Source\":\"user\",\"ID\":\"displayname\",\"SamlClaimType\":\"full_name\"},{\"Source\":\"user\",\"ID\":\"assignedroles\",\"SamlClaimType\":\"roles\"},{\"Source\":\"user\",\"ID\":\"mail\",\"SamlClaimType\":\"email\"},{\"Source\":\"user\",\"ExtensionID\":\"extension_xxx_exampleproperty\",\"SamlClaimType\":\"exampleclaimattribute\"}]}}"
8
],
9
"displayName": "Example Claim Policy Name",
10
"isOrganizationDefault": false
11
}

Assign Claims Mapping Policy to Application

assign-claims-mapping-policy-to-application page anchor

Finally, we can assign the Claims Mapping Policy we just created to your Enterprise Application.

(error)

Danger

Once you assign a Claims Mapping Policy to an Enterprise Application using Graph API or Powershell, you can no longer manage SAML claims in the Azure AD portal. Accessing the SAML claims configuration page in the Azure AD portal will result in the following error message:

"This configuration was overwritten by a claim mapping policy created via Graph/PowerShell."

Run the following Graph Explorer query. Make sure to:

  • Replace <Service_Principal_Entity_ID> with your previously retrieved Service Principal Entity ID from the "Gather supporting IDs" section.

  • Click on the Request body tab to add the JSON in the Request body field.

    • Replace <Claims_Mapping_Policy_ID> with the ID you noted previously.
1
HTTP method: POST
2
API version: v1.0
3
Endpoint URL: https://graph.microsoft.com/v1.0/servicePrincipals/<Service_Principal_Entity_ID>/claimsMappingPolicies/$ref
4
Request body:
5
{
6
"@odata.id": "https://graph.microsoft.com/v1.0/policies/claimsMappingPolicies/<Claims_Mapping_Policy_ID>"
7
}
8
  • Click Run query and in the JSON response, look for the array object whose "appDisplayName" value matches your Enterprise Application name.
  • Copy the "id" value and paste it somewhere you can reference it.
204 {}

Test New Claims Mapping Policy

test-new-claims-mapping-policy page anchor

With all of the above sections completed, you are now ready to login to Flex and test the new claims mapping policy.

  1. Navigate to the Flex Console Single Sign-on settings page(link takes you to an external page).

  2. When redirected to Azure AD for authentication, log in with the AD user account you assigned the extension property to previously in this guide.

  3. After logging in and the Flex UI has loaded, check the TaskRouter worker attributes in the Twilio Console ( TaskRouter -> Workspace -> Workers).

    If you're seeing all the expected attributes you've defined in the Claims Mapping Policy, you have successfully configured your custom claims.

    If any attributes are missing, examine the SAML response payload after authenticating to Azure AD. Ensure all the attributes are included, and that they have values, and that the "Name" parameter is correct for each of them.

    As explained in the Twilio Flex SSO and IdP troubleshooting section, if the "Name" parameter on an attribute includes the namespace, it will be ignored. Double check your Claims Mapping Policy definition and ensure the "SamlClaimType" is defined without a namespace for each claim you want to be captured as a TaskRouter worker attribute.

Unassign Claims Mapping Policy from Application

unassign-claims-mapping-policy-from-application page anchor

If you no longer need or want to pass custom extension properties as SAML claims and you'd like to manage claims in the Azure AD portal, or you want to assign a different Claims Mapping Policy to your Enterprise application, you will need to unassign the current Claims Mapping Policy from the application.

Run the following Graph Explorer query. Make sure to:

  • In the URL field, replace <Service_Principal_Entity_ID> with your previously retrieved Service Principal Entity ID from the "Gather supporting IDs" section.
1
HTTP method: DELETE
2
API version: v1.0
3
Endpoint URL: https://graph.microsoft.com/v1.0/servicePrincipals/<Service_Principal_Entity_ID>/claimsMappingPolicies/<Claims_Mapping_Policy_ID>/$ref
4

Click Run query.

204 {}

To verify there are no Claims Mapping Policies assigned to your application, run the following query using the same Service Principal Entity ID.

1
HTTP method: GET
2
API version: 1.0
3
Endpoint URL: https://graph.microsoft.com/v1.0/servicePrincipals/<Service_Principal_Entity_ID>/claimsMappingPolicies
4

In the JSON response, the "value" property should now show an empty array.

Example Claims Policy Definition

example-claims-policy-definition page anchor

For the claim definition JSON object in "Create a Claims Mapping Policy", let's walk through an example definition and some key configuration properties.

(warning)

Warning

For walkthrough purposes, we are displaying an unminified and unescaped claims policy definition. When creating a claims policy in Graph Explorer, you need to minify and escape the JSON object before adding it to the "definition" array. This removes traces of offending characters that could prevent parsing.

1
{
2
"ClaimsMappingPolicy": {
3
"Version": 1,
4
"IncludeBasicClaimSet": "true",
5
"ClaimsSchema": [
6
{
7
"Source": "user",
8
"ID": "userprincipalname",
9
"SamlClaimType": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier"
10
},
11
{
12
"Source": "user",
13
"ID": "displayname",
14
"SamlClaimType": "full_name"
15
},
16
{
17
"Source": "user",
18
"ID": "assignedroles",
19
"SamlClaimType": "roles"
20
},
21
{
22
"Source": "user",
23
"ID": "mail",
24
"SamlClaimType": "email"
25
}, {
26
"Source": "user",
27
"ExtensionID": "extension__",
28
"SamlClaimType": ""
29
}
30
]
31
}
32
}
33
34
}

Let's take a look at the key properties in the example definition:

Property: IncludeBasicClaimSet

Description:

Controls whether all claims in the basic claim set are included in the token generated by applications using this policy. For more details, see Azure's documentation(link takes you to an external page).

Property: ClaimsSchema

Description:

Make sure to include the required attributes for Flex:email, full_name, and roles. In the Claims schema of our example definition, each item includes the following attributes:

  • "Source": The source AD object. In this example, all of the claims we're passing are coming from the "user" object.

  • "ID": For standard AD user properties, "ID" is the key and the value is the user property we're interested in For example, to pass the display name of a user, we use { “Source”: “user”, “ID”: “displayname” }, telling the policy to use the "user.displayname" property for this claim.

  • "ExtensionID": For extension properties, like the one we created earlier in this guide, "ExtensionID" is the key and the value is the extension property in the format "extension_<App_ID>_<Property_Name>".

  • "SamlClaimType": The value that will be passed in the "name" parameter of the claim attribute This value is what will be used by Flex for populating the TaskRouter worker attribute. For Flex to process it as a worker attribute, **it must be the name only, not preceded by a namespace. **For example, if we need to populate the "team_id" TaskRouter worker attribute with a custom extension property, we use { "Source": "user", "ExtensionID": "extension_<App_ID>_<Property_Name>", "SamlClaimType": "team_id" }, telling the policy to pass a claim attribute with Name="team_id" populated with the value contained in "user.extension_<App_ID>_<Property_Name>".

Need some help?

Terms of service

Copyright © 2024 Twilio Inc.