Out-of-the-box AAD B2C does not expose any functionality related to Security Groups. They exist as an entity type and can be accessed via the regular Azure AD portal blade but there are no features for including user group membership in a token issued as a result of a user flow. To use groups you will need to add some custom code through custom (IEF) policies. Here is a description of how I accomplished that.
- Use regular AAD portal blade (e.g. https://add.portal.azure.com ), PowerShell, or Graph API to create groups and assign users to these.
- Create a set of custom policies (https://docs.microsoft.com/en-us/azure/active-directory-b2c/active-directory-b2c-get-started-custom).
- Add a new claim type of type stringCollection:
<ClaimType Id="groups">
<DisplayName>Comma delimited list of group names</DisplayName>
<DataType>stringCollection</DataType>
<UserInputType>Readonly</UserInputType>
</ClaimType>
- Create a REST function to retrieve a user’s group memberships using Graph API. Here, the application operates using a service principal (application token) and is registered in the B2C tenant using the regular AAD blade as shown here: https://docs.microsoft.com/en-us/azure/active-directory-b2c/active-directory-b2c-devquickstarts-graph-dotnet .
[HttpGet]
[Route("Groups")]
public async Task<ActionResult<RESTResponse>> Groups(string objectId)
{
await GetAccessTokenAsync();
var http = new HttpClient();
http.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", _GraphToken);
var queryString = $"users/{userObjectId}/$links/memberOf?api-version=1.6";
// GET https://graph.windows.net/myorganization/users/{user_id}/$links/memberOf?api-version
var resp = await http.GetStringAsync($"https://graph.windows.net/{_settings.domain}.onmicrosoft.com/{queryString}");
var groupArray = (JArray)JObject.Parse(resp)["value"];
var groups = new List<string>();
foreach (JObject g in groupArray)
{
var groupUrl = g["url"].Value<string>();
var groupResp = await http.GetStringAsync($"{groupUrl}?api-version=1.6&$select=displayName");
var name = JObject.Parse(groupResp)["displayName"].Value<string>();
result.Add(name);
}
return new JsonResult(
new
{
groups
});
}
- Create a Technical Profile in your Base or Extension xml file to call this function from your user journeys:
<TechnicalProfile Id="GetUserGroups">
<DisplayName>Retrieves security groups assigned to the user</DisplayName>
<Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.RestfulProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
<Metadata>
<Item Key="ServiceUrl">https://xyz.azurewebsites.net/b2cdata/groups</Item>
<Item Key="AuthenticationType">None</Item>
<Item Key="SendClaimsIn">QueryString</Item>
<Item Key="AllowInsecureAuthInProduction">true</Item>
</Metadata>
<InputClaims>
<InputClaim Required="true" ClaimTypeReferenceId="objectId" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="groups" />
</OutputClaims>
<UseTechnicalProfileForSessionManagement ReferenceId="SM-Noop" />
</TechnicalProfile>
- Call the REST function from your signin user journey in a step just before the step where you send claims (or if you want to be fancy add additional steps to determine whether a token even can be issued based on group membership):
<OrchestrationStep Order="5" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="GetUserGroups" TechnicalProfileReferenceId="GetUserGroups" />
</ClaimsExchanges>
</OrchestrationStep>
Your JWT token should now contain the groups claim, e.g.:
{
"exp": 1556819742,
"nbf": 1556816142,
"ver": "1.0",
"iss": "https://xyz.b2clogin.com/xyz.onmicrosoft.com/v2.0/",
"sub": "b7a1cb1e-d8d9-41c3-af94-aac0c9c0387c",
"aud": "e4535fdc-6ae7-4d65-9c5c-bf79b136f932",
"acr": "b2c_1a_hmhssignin",
"groups": [
"Guests",
"B2CTest1"
],
"tid": "cf6c572c-c72e-4f31-bd0b-75623d040495"
}