AAD B2C is more than just an identity as a service, it is a customer identity access management (CIAM) solution but to unleash its true power, you have to get into custom policies and write some XML.
I have come across scenarios where customers don't consider B2C if its not offered by OOB User Flows and the most common ask is for App Roles and AAD groups. Taking that into consideration, i have worked on this scenario which provides an easy means of getting AAD group membership for a user.
The code provided includes Azure Function and B2C custom policies. There are some perquisites for IEF which are well documented already.
Objective is to get a token which have a list of groups that the user is member of. The way I have implemented this, is by using B2C Custom Policies which calls an Azure Function through a Technical Profile. Azure Function makes use of Microsoft Graph to obtain a list of groups which is passed back to the Technical Profile.
Azure Function utilizes an App registration in Azure AD (B2C) in order get the group membership of the user. Therefore, we need an App registration with Application permissions to support this. For details on permissions check user: getMemberGroups. Once we have the App registration, we can set the values in local.settings.json file which is good enough to run Azure Function locally but when you publish it, you will have to update the configuration section of Azure Function in the portal and add application settings with the same variables and values.
{
"IsEncrypted": false,
"Values": {
"AzureWebJobsStorage": "DefaultEndpointsProtocol=https;AccountName=storageacname;AccountKey=xxxxxxxxxxx;EndpointSuffix=core.windows.net",
"FUNCTIONS_WORKER_RUNTIME": "dotnet",
"Instance": "https://login.microsoftonline.com/{0}",
"ApiUrl": "https://graph.microsoft.com/",
"Dest_Tenant": "xxxxxxx-xxx-xxxx-xxxx-xxxxxxxxxx",
"Dest_ClientId": "xxxxxxx-xxxxx-xxxx-xxxx-xxxxxxxxxxx",
"Dest_ClientSecret": "xxxxxxxxxxxxxxxx"
}
}
I have used B2C starter pack to begin with because you don't want to write these files from the scratch. There are multiple folders in that, for simplicity I am using the LocalAccounts.
The three files which we need to test our scenario are SignUpOrSignin.xml (aka RP file), TrustFrameworkExtensions.xml (aka Extensions file) and TrustFrameworkBase.xml (aka Base file). Most of the modification is done in the TrustFrameworkExtensions.xml file.
To start with, I have cut the signUporSignIn UserJourney from the Base file into the Extensions file and added an Orchestration step which references a Technical Profile called "REST-CallFuncApp". I have adjusted the order number accordingly.
<OrchestrationStep Order="4" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="RESTCallFuncApp" TechnicalProfileReferenceId="REST-CallFuncApp" />
</ClaimsExchanges>
</OrchestrationStep>
Technical profile is just like a function which takes input claims and may provide some output claims after processing. In our case, we are calling an Azure Function which is handled by RestfulProvider. Each Technical Profile will have a protocol and the handler which we dont have to configure in this case. However, we have to configure the metadata and input/output claims. In this case input claim is what we are sending to Azure Function and metadata defines the configuration related to REST API call.
<TechnicalProfile Id="REST-CallFuncApp">
<DisplayName>Return groups claim</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://b2crestfull.azurewebsites.net/api/FnB2CFindUserGroups?code=yourFunctionAccessKey</Item>
<Item Key="SendClaimsIn">Body</Item>
<!-- Set AuthenticationType to Basic or ClientCertificate in production environments -->
<Item Key="AuthenticationType">None</Item>
<!-- REMOVE the following line in production environments -->
<Item Key="AllowInsecureAuthInProduction">true</Item>
</Metadata>
<InputClaims>
<InputClaim ClaimTypeReferenceId="objectId" />
</InputClaims>
<OutputClaims>
<!-- Claims parsed from your REST API -->
<OutputClaim ClaimTypeReferenceId="groups" />
</OutputClaims>
</TechnicalProfile>
As output, we get the list of group Id's which we store in an output claim called groups which is of data type stringCollection. Anything in the output claim is available in the Claims Bag and can be sent back to the Relying Party.
<BuildingBlocks>
<ClaimsSchema>
<ClaimType Id="groups">
<DisplayName>Your Groups</DisplayName>
<DataType>stringCollection</DataType>
</ClaimType>
</ClaimsSchema>
</BuildingBlocks>
Lastly, I have configured the Relying Party in the file SignUpOrSignin.xml to send groups claim in the jwt Token.
<RelyingParty>
<DefaultUserJourney ReferenceId="SignUpOrSignIn" />
<TechnicalProfile Id="PolicyProfile">
<DisplayName>PolicyProfile</DisplayName>
<Protocol Name="OpenIdConnect" />
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="displayName" />
<OutputClaim ClaimTypeReferenceId="givenName" />
<OutputClaim ClaimTypeReferenceId="surname" />
<OutputClaim ClaimTypeReferenceId="email" />
<OutputClaim ClaimTypeReferenceId="groups" DefaultValue="" />
<OutputClaim ClaimTypeReferenceId="objectId" PartnerClaimType="sub"/>
<OutputClaim ClaimTypeReferenceId="tenantId" AlwaysUseDefaultValue="true" DefaultValue="{Policy:TenantObjectId}" />
</OutputClaims>
<SubjectNamingInfo ClaimType="sub" />
</TechnicalProfile>
</RelyingParty>
And now when you sign in to your application, you will get a Token with a claim called groups which will have your group Id's.
{
"typ": "JWT",
"alg": "RS256",
"kid": "hd0o7C4Fkbstrc-FpJ5y6zQvi1ekBjyELKHScfJ7pho"
}.{
"exp": 1630734610,
"nbf": 1630731010,
"ver": "1.0",
"iss": "https://B2Ctenant.b2clogin.com/367886bf-2d02-48f8-xxxxxxxxxxxxxxx/v2.0/",
"sub": "57b9d8f4-b5d7-424b-xxxxxxxxxxxxxxxx",
"aud": "57c11075-4193-43b0-xxxxxxxxxxxxxxxx",
"acr": "b2c_1a_signup_signin",
"nonce": "defaultNonce",
"iat": 1630731010,
"auth_time": 1630731010,
"name": "Alice Bob",
"given_name": "Alice",
"family_name": "Bob",
"groups": [
"755f2a24-705d-4ae9-af01-47155b0abe99"
],
"tid": "367886bf-2d02-48f8-xxxxxxxxxxxxxx"
}.[Signature]