Add Role to Existing User in Azure AD B2C using Graph SDK - azure-ad-b2c

We are using GRAPH SDK to manage users In Azure AD B2C.
We have been able to create an user account and now want to assign a role to the user.
We have referred the documentation but not able to find the way forward.
Is there a way we can assign role to a user using Graph SDK
Any help on this would be appreciated

Azure AD B2C does not really support roles out of the box sadly. There are a couple of ways to do this. If you want to add the roles to your token on sign-in, then you will have to use custom policies and an API call (or Azure Function) to add roles to your claims. If you are new to custom policies, I highly recommend reading the official documentation on custom policies first.
If you only want to manage roles without adding them to your token on sign-in. You can create Security groups in your ActiveDirectory (In the same directory as your B2C tenant). This should look something like this.
You can then use the Get Groups call. or list memberOf call.
GET /groups/{id}
GET /me/memberOf
GET /users/{id | userPrincipalName}/memberOf
Ofcourse you can also add users to the groups
POST /groups/{group-id}/members/$ref
If you do want to add the roles to your token on sign-in, I personally used the video. https://www.youtube.com/watch?v=C9qN6QqnxQ8 This uses an Azure function but you can also use your own API if you want to.
IMPORTANT: You cannot test calls to your own API with localhost in custom policies, so make sure to make it available with something like ngrok.
I will describe the steps as well: First add the role (or roles) ClaimTypeReferenceId to your SignUp.xml
<TechnicalProfile Id="PolicyProfile">
<DisplayName>PolicyProfile</DisplayName>
<Protocol Name="OpenIdConnect" />
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="email" />
Other claims
<OutputClaim ClaimTypeReferenceId="role" />
</OutputClaims>
<SubjectNamingInfo ClaimType="sub" />
</TechnicalProfile>
In the TrustFrameworkExtensions.xml add the claimtype role (or roles)
<ClaimType Id="role">
<DisplayName>Role</DisplayName>
<DataType>stringCollection</DataType> <!-- or string if you only want 1 role -->
</ClaimType>
Add the Rest API call to your TrustFrameworkExtensions.xml. This sets the Parameters of your API call to an email address which you have received from the sign-in, and it expects a role as a result.
<ClaimsProvider>
<DisplayName>REST APIs</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="REST-GetProfile">
<DisplayName>Get user extended profile Azure Function web hook</DisplayName>
<Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.RestfulProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
<Metadata>
<!-- Set the ServiceUrl with your own REST API endpoint -->
<Item Key="ServiceUrl">https://functions.azurewebsites.net/api/HttpTriggerGetRoles?code=YOURCODE</Item> <!-- TODO CHANGE THIS URL to your own-->
<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>
<!-- Claims sent to your REST API -->
<InputClaim ClaimTypeReferenceId="signInNames.emailAddress" PartnerClaimType="emails" />
<InputClaim ClaimTypeReferenceId="email" PartnerClaimType="email" />
</InputClaims>
<OutputClaims>
<!-- Claims parsed from your REST API -->
<OutputClaim ClaimTypeReferenceId="role" PartnerClaimType="role"/>
</OutputClaims>
<UseTechnicalProfileForSessionManagement ReferenceId="SM-Noop" />
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
Lastly, add the GetRoleData TechnicalProfile to your second to last OrchestrationStep
<OrchestrationStep Order="10" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="GetRoleData" TechnicalProfileReferenceId="REST-GetProfile" />
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="11" Type="SendClaims" CpimIssuerTechnicalProfileReferenceId="JwtIssuer" />
For testing I created an Azure Function with the following code in C#. In this example I just put it hard coded. But you could make a call to the graph API from here.
#r "Newtonsoft.Json"
using System.Net;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Primitives;
using Newtonsoft.Json;
public static IActionResult Run(HttpRequest req, ILogger log)
{
log.LogInformation("C# HTTP trigger function processed a request.");
string[] roles = {"Admin", "Employee"};
var adminrole = new {
role = roles
};
return new OkObjectResult(adminrole);
}
// string email = req.Query["email"];
// string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
// dynamic data = JsonConvert.DeserializeObject(requestBody);
// email = email ?? data?.email;
Your token should now contain the roles.
{
"typ": "JWT",
"alg": "RS256",
"kid": "H-0"
}.{
"iss": "https://demo.b2clogin.com/9729/v2.0/",
"exp": 164,
"nbf": 1634,
"aud": "4a12ea396",
"given_name": "Tim C",
"family_name": "C",
"name": "Tim",
"idp": "google.com",
"sub": "adc1",
"role": [
"Admin",
"Employee"
],
"scp": "demo.write demo.read",
"ver": "1.0",
}.[Signature]

Related

How Do I Whitelist Email Domains In Signup With Azure B2C?

I am using simple email signup for Azure B2C and not SSO and my user flow is the basic one from this tutorial
https://learn.microsoft.com/en-ca/azure/active-directory-b2c/tutorial-create-user-flows
At the moment I am using temporary email addresses for my testing, however, when I go live I want to whitelist the domains that people can sign up with.
How do I do this whitelisting?
Currently Using Azure AD B2C Userflows we can't Whitelist the users while signup based on the email domain. We Need to handle it using Custom policies by calling the Rest API and need to validate the email address.
Please go through the documents and sample on using Custom policies with Rest API.
A Restful technical profile provides support for interfacing with your own RESTful service. Azure AD B2C sends data to the RESTful service in an InputClaims collection and receives data back in an OutputClaims collection. Find the ClaimsProviders element in your
Configure the REST API technical profile
<ClaimsProvider>
<DisplayName>REST APIs</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="REST-GetProfile">
<DisplayName>Get user extended profile Azure Function web hook</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://your-account.azurewebsites.net/api/GetProfile?code=your-code</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>
<!-- Claims sent to your REST API -->
<InputClaim ClaimTypeReferenceId="objectId" />
<InputClaim ClaimTypeReferenceId="userLanguage" PartnerClaimType="lang" DefaultValue="{Culture:LCID}" AlwaysUseDefaultValue="true" />
</InputClaims>
<OutputClaims>
<!-- Claims parsed from your REST API -->
<OutputClaim ClaimTypeReferenceId="balance" />
</OutputClaims>
<UseTechnicalProfileForSessionManagement ReferenceId="SM-Noop" />
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
You need to handle the Email validation logic in your REST API. The above Provideed document links helps you in building the custom policy.
This limitation is already raised in the Azure Feedback uservoice and to consider this feature request for the future, please upvote on the existing UserVoice.
That way the product group can prioritize this accordingly.

Azure B2C - Send Groups In Claims to SAML SP Using REST API That Queries Graph

I am trying to return group claim in a SignupSign custom policy in Azure B2C to the SAML test app found here https://samltestapp2.azurewebsites.net/. Here's of what I have done so far: -
Created a HTTP trigger PowerShell Azure Function that returns a comma-separated list of groups a user belongs to after querying the MS Graph API. I call it outside B2C and it works fine.
In the TrustFrameworkExtensions.xml file, added a claim type element like so
<BuildingBlocks>
<ClaimsSchema>
<ClaimType Id="groups">
<DisplayName>Comma delimited list of group names</DisplayName>
<DataType>stringCollection</DataType>
<DefaultPartnerClaimTypes>
<Protocol Name="OAuth2" PartnerClaimType="groups" />
<Protocol Name="OpenIdConnect" PartnerClaimType="groups" />
<Protocol Name="SAML2" PartnerClaimType="http://schemas.microsoft.com/ws/2008/06/identity/claims/groups" />
</DefaultPartnerClaimTypes>
<UserInputType>Readonly</UserInputType>
</ClaimType>
</ClaimsSchema>
In the TrustFrameworkExtensions.xml I also added a Technical profile and an orchestration step like so
<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://morphitgraphapicall.azurewebsites.net/api/GetGroupsFromGraph?objectID=objectId</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>
...
<OrchestrationStep Order="7" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="GetUserGroups" TechnicalProfileReferenceId="GetUserGroups" />
</ClaimsExchanges>
</OrchestrationStep>
In the relying party file, added an output claim like so
relying party file output claim
But I can't seem to get the groups claim passed to the SP application. I can tell that the REST API is being called because being on the Azure Functions consumption plan, there is a cold start if I haven't called the function for a bit which leads to a pause after sign-in.
Thank you.

How can I read the mobile value from a B2C user record and transform it to a strongAuthenticationPhoneNumber?

I have the need to create Azure B2C user accounts programmatically. In a separate user data store I hold pertinent information about the users I need to set up in B2C including their mobile phone number, which we've already been communicating with them on.
My business requirement is that this mobile phone number is used as a secondary factor during the user's first-time login/password reset experience. I have an initial login experience which uses an externally-created JWT token to take the user to a custom User Journey where they can set a password for the first time.
I understand that it is not yet possible to set the Azure MFA mobile number via Graph API or PowerShell. (Is this still true?). Therefore B2C asks the user to enter their mobile number in the exemplar PhoneFactor-InputOrVerify Technical Profile. This is a security hole as you can just enter any mobile number in there and verify that number.
I can easily programmatically add the user's number to some other field - e.g. the mobile field on the user record.
Question 1.
Is there a way to read the user account mobile value and present it to a Technical Profile as if it is the strongAuthenticationPhoneNumber value or Verified.strongAuthenticationPhoneNumber?
Question 2.
Is this even a good idea? I imagine there are good reasons not to do this, but I can't fathom what they might be.
I've tried creating new ClaimTypes, reading the 'mobile' field value, creating ClaimsTranfromations to try to make the mobile claim appear to be the strongAuthenticationPhoneNumber claim, and trying generally to 'spoof' B2C into thinking this is the actual number as stored in the MFA data store.
This is the the standard PhoneFactor-InputOrVerify Technical Profile from the starterpack:
<ClaimsProvider>
<DisplayName>PhoneFactor</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="PhoneFactor-InputOrVerify">
<DisplayName>PhoneFactor</DisplayName>
<Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.PhoneFactorProtocolProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
<Metadata>
<Item Key="ContentDefinitionReferenceId">api.phonefactor</Item>
<Item Key="ManualPhoneNumberEntryAllowed">true</Item>
</Metadata>
<CryptographicKeys>
<Key Id="issuer_secret" StorageReferenceId="B2C_1A_TokenSigningKeyContainer" />
</CryptographicKeys>
<InputClaimsTransformations>
<InputClaimsTransformation ReferenceId="CreateUserIdForMFA" />
</InputClaimsTransformations>
<InputClaims>
<InputClaim ClaimTypeReferenceId="userIdForMFA" PartnerClaimType="UserId" />
<InputClaim ClaimTypeReferenceId="strongAuthenticationPhoneNumber" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="Verified.strongAuthenticationPhoneNumber" PartnerClaimType="Verified.OfficePhone" />
<OutputClaim ClaimTypeReferenceId="newPhoneNumberEntered" PartnerClaimType="newPhoneNumberEntered" />
</OutputClaims>
<UseTechnicalProfileForSessionManagement ReferenceId="SM-MFA" />
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
I can provide more code samples of the custom User Journey I mentioned earlier but I don't think this will help with this problem.
You have a few options:
You can add the strongAuthenticationPhoneNumber claim to the same JWT that is used for the onboarding flow and prompt for verification of this phone number during this onboarding flow.
You can map to the strongAuthenticationPhoneNumber claim for the PhoneFactor-InputOrVerify technical profile from the mobile property (or an extension property) of the user object.
For option 1, the onboarding user journey should write the verified phone number to the user object, without the newPhoneNumberEntered-based precondition:
<OrchestrationStep Order="8" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="AADUserWriteWithObjectId" TechnicalProfileReferenceId="AAD-UserWritePhoneNumberUsingObjectId" />
</ClaimsExchanges>
</OrchestrationStep>
For option 2, you can map to the strongAuthenticationPhoneNumber claim from the mobile property, as follows:
<InputClaims>
<InputClaim ClaimTypeReferenceId="userIdForMFA" PartnerClaimType="UserId" />
<InputClaim ClaimTypeReferenceId="mobile" PartnerClaimType="strongAuthenticationPhoneNumber" />
</InputClaims>

Azure B2C - Accept query params into OAuth2 JWT

I was curious if it was possible to read query parameters when requesting an OAuth2 token through Azure?
Essentially, when making a test call with a policy that I created, I would like to have an additional query parameters read from the call and the orchestration (user journey) steps should read these values and inject that value into a custom claim (for the JWT or ID token).
I know from the follow links that it may* be possible with Azure B2C service? But I can't find any good concrete examples.
Sign-up policy - Set user attributes through code
Add Custom Attribute Not Used in Sign-Up nor Edit Policy
How can I return the PolicyId Claim after executing my Custom SignUpSignIn policy?
How do i include email in the redirect to AZURE AD B2C
I then proceeded in trying a bunch of configurations out but there are so many options to choose from, I don't know which to choose. In addition, I haven't been able to find any Azure docs that describe the options used when configuring these policies. In any case, here is what I have.
I downloaded the TrustFrameworkBase.xml and TrustFrameworkExtensions.xml from here. I got this Github link from this Azure doc, which I also followed the steps on setting up policy keys and added an app registration with delegated permissions. For my relying party configuration, I simply made a custom policy through the Azure B2C portal and downloaded it as a starting point to explore what it looks like in a basic form.
Here is my custom claim added to the base policy within the ClaimsSchema tag. extension_Test is the claim where I want to inject the value from a query param:
<ClaimType Id="extension_Test">
<DisplayName>Test value</DisplayName>
<DataType>string</DataType>
<DefaultPartnerClaimTypes>
<Protocol Name="OAuth2" PartnerClaimType="extension_Test" />
<Protocol Name="OpenIdConnect" PartnerClaimType="extension_Test" />
</DefaultPartnerClaimTypes>
<UserInputType>Readonly</UserInputType>
</ClaimType>
</ClaimsSchema>
In the same base policy, here's the userjourney that I added for SignIn:
<UserJourney Id="SignIn">
<OrchestrationSteps>
<OrchestrationSteps>
<!-- The following orchestration step is always executed. -->
<OrchestrationStep Order="1" Type="ClaimsProviderSelection" ContentDefinitionReferenceId="api.idpselection.signupsignin">
<ClaimsProviderSelections>
<ClaimsProviderSelection TargetClaimsExchangeId="LocalAccountRegistrationExchange" />
</ClaimsProviderSelections>
</OrchestrationStep>
<OrchestrationStep Order="2" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="LocalAccountRegistrationExchange" TechnicalProfileReferenceId="LocalAccount-Registration-VerifiedEmail" />
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="3" Type="SendClaims" CpimIssuerTechnicalProfileReferenceId="JwtIssuer" />
</OrchestrationSteps>
</UserJourney>
Here's my relying config XML:
<RelyingParty>
<DefaultUserJourney ReferenceId="SignIn" />
<TechnicalProfile Id="PolicyProfile">
<DisplayName>PolicyProfile</DisplayName>
<Protocol Name="OpenIdConnect" />
<InputClaims>
<InputClaim ClaimTypeReferenceId="extension_Test" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="displayName" />
<OutputClaim ClaimTypeReferenceId="givenName" />
<OutputClaim ClaimTypeReferenceId="surname" />
<OutputClaim ClaimTypeReferenceId="extension_Test" />
<OutputClaim ClaimTypeReferenceId="objectId" PartnerClaimType="sub" />
</OutputClaims>
<SubjectNamingInfo ClaimType="sub" />
</TechnicalProfile>
</RelyingParty>
I think uploaded the base, extensions and RP policies XML files in that order. The GET request that I'm sending looking like this (got from the "Run now" button for the custom policy):
https://login.microsoftonline.com/<TENANT>/oauth2/v2.0/authorize?p=B2C_1A_test&client_id=<TENANTID>&nonce=defaultNonce&redirect_uri=http%3A%2F%2Flocalhost%2Fredirect&scope=openid&response_type=id_token&prompt=login&extension_Test=aaa
Any help would be greatly appreciated, thanks! Or Azure documents that explain more options within these config files - as in what does CpimIssuerTechnicalProfileReferenceId="JwtIssuer" mean? Or AzureFunction-WrapWebHook mean?
You are close.
An end-to-end example of inputting a claim to a journey, and then using it in this journey (e.g. pre-conditions or storage) as well as outputting it from the journey, can be found in this "Implementing an invitation flow" document (which I was author of).
The high-level solution is:
1) At design-time, configure the relying party policy with the input claim.
<RelyingParty>
<DefaultUserJourney ReferenceId="SignIn" />
<TechnicalProfile Id="PolicyProfile">
<DisplayName>PolicyProfile</DisplayName>
<Protocol Name="OpenIdConnect" />
<InputTokenFormat>JWT</InputTokenFormat>
<CryptographicKeys>
<Key Id="client_secret" StorageReferenceId="B2C_1A_MySharedSecret" />
</CryptographicKeys>
<InputClaims>
<InputClaim ClaimTypeReferenceId="extension_Test" />
</InputClaims>
<OutputClaims>
...
<OutputClaim ClaimTypeReferenceId="extension_Test" />
</OutputClaims>
<SubjectNamingInfo ClaimType="sub" />
</TechnicalProfile>
</RelyingParty>
You must create a policy key (in the above example, this is called "MySharedSecret", but it can be called anything) containing a shared secret that is known to the application that is invoking this policy (where the client secret for this application can be this shared secret).
2) At runtime, create a self-issued JWT containing the input claim, sign this JWT with the shared secret, and then add the JWT to the authentication request using the "client_assertion_type" and "client_assertion" parameters.
The code example for this can be found in the Wingtip sample.
An example of the authentication request is:
https://login.microsoftonline.com/b2ctechready.onmicrosoft.com/oauth2/v2.0/authorize?p=b2c_1a_invitation&...&client_assertion_type=urn%3Aietf%3Aparams%3Aoauth%3Aclient-assertion-type%3Ajwt-bearer&client_assertion=eyJhbGci...7m9s&state=CfDJ8EPk...Et0w

Reading Extension Claims in Azure AD B2C

I have 2 claims that I want to store in the Directory for my application to use. These are not available for the user to edit however is available for the application to read from the Token.
The BuiltIn policies are able to retrieve the claims however, I have not had any success with retrieving these claims using Custom Policies.
Reading through Next Steps of the article “Creating and using custom attributes in a custom profile edit policy” the claims will need to be added to the RP and TechnicalProfile to read from Directory. I accordingly updated the RP and as well the TP's that read from Directory such as
<TechnicalProfile Id="AAD-UserReadUsingAlternativeSecurityId">
<TechnicalProfile Id="AAD-UserReadUsingObjectId">
<TechnicalProfile Id="SelfAsserted-LocalAccountSignin-Email">
<TechnicalProfile Id="AAD-UserReadUsingEmailAddress">
Unable to figure out what might be missing to retreive the 2 extension claims.
Assuming you are reading the custom claims in the user journeys and writing them via the Azure AD Graph API, then you must:
1: Add the custom claims as <ClaimType />s to the base policy.
<ClaimType Id="extension_UserAttribute1">
<DisplayName>User Attribute 1</DisplayName>
<DataType>string</DataType>
</ClaimType>
<ClaimType Id="extension_UserAttribute2">
<DisplayName>User Attribute 2</DisplayName>
<DataType>string</DataType>
</ClaimType>
2: Add the application and object identifiers for the extensions app to the "AAD-Common" technical profile which is required to read the custom claims from the Azure AD B2C directory.
<TechnicalProfile Id="AAD-Common">
<DisplayName>Azure Active Directory</DisplayName>
<Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.AzureActiveDirectoryProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
<Metadata>
<Item Key="ApplicationObjectId">Insert the object identifier for the b2c-extensions-app application here</Item>
<Item Key="ClientId">Insert the application identifier for the b2c-extensions-app application here</Item>
</Metadata>
<CryptographicKeys>
<Key Id="issuer_secret" StorageReferenceId="TokenSigningKeyContainer" />
</CryptographicKeys>
...
</TechnicalProfile>
Note: If you are wanting to read the custom claims in both built-in policies and custom policies, then you must use the application and object identifiers for the built-in b2c-extensions-app application rather than a custom extensions app as suggested by the Azure Active Directory B2C: Creating and using custom attributes in a custom profile edit policy tutorial.
3: Add the custom claims as <OutputClaim />s to the following technical profiles:
"AAD-UserReadUsingObjectId" for local account sign-in and profile editing
"AAD-UserReadUsingAlternativeSecurityId" for a social account sign-in and profile editing
"LocalAccountDiscoveryUsingEmailAddress" and "AAD-UserReadUsingEmailAddress" for a local account password reset
<OutputClaims>
...
<OutputClaim ClaimTypeReferenceId="extension_UserAttribute1" />
<OutputClaim ClaimTypeReferenceId="extension_UserAttribute2" />
</OutputClaims>
4: Issue the custom claims as <OutputClaim />s in any relying party policies.
Thanks #ChrisPadget. For anybody still struggling. Make sure that the UserJourney Step that reads data from AD is actually available in your User Journey. In my case, I had to add:
<OrchestrationStep Order="2" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="AADUserReadWithObjectId" TechnicalProfileReferenceId="AAD-UserReadUsingObjectId" />
</ClaimsExchanges>
</OrchestrationStep>

Resources