Azure AD B2C - Refresh_Token refresh claims via REST (Identity Experience Framework) - azure-ad-b2c

We have Azure AD B2C setup to use Identity Experience Framework, and on sign-in/sign-up a REST call is made to get extra security credential claims via an Azure Function. This works fine.
When we request an Access/Id Token via Refresh_Token via Azure AD B2C it looks like we get the same token back, and it doesn't call the REST API to get the latest updated token claims. Is it possible to make change this User Journey so it does?
Is there another solution to refresh token without logging in again to get latest updates?
(We could get around this in code and not using the Token, but for various reasons we want to explore this first).

You can declare a refresh token user journey, which calls your REST API, as follows:
<UserJourney Id="TokenRefresh">
<PreserveOriginalAssertion>false</PreserveOriginalAssertion>
<OrchestrationSteps>
<OrchestrationStep Order="1" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="RefreshTokenExchange" TechnicalProfileReferenceId="TpEngine_RefreshToken" />
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="2" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="AADUserReadWithObjectId" TechnicalProfileReferenceId="AAD-UserReadUsingObjectId" />
</ClaimsExchanges>
</OrchestrationStep>
<!-- TODO: Add an orchestration step that calls the REST API. -->
<OrchestrationStep Order="3" Type="SendClaims" CpimIssuerTechnicalProfileReferenceId="JwtIssuer" />
</OrchestrationSteps>
</UserJourney>
The initial orchestration step invokes the TpEngine_RefreshToken technical profile that reads the objectId claim from the current refresh token:
<ClaimsProvider>
<DisplayName>Trustframework Policy Engine Technical Profiles</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="TpEngine_c3bd4fe2-1775-4013-b91d-35f16d377d13">
<DisplayName>Trustframework Policy Engine Default Technical Profile</DisplayName>
<Protocol Name="None" />
<Metadata>
<Item Key="url">{service:te}</Item>
</Metadata>
</TechnicalProfile>
<TechnicalProfile Id="TpEngine_RefreshToken">
<DisplayName>Trustframework Policy Engine Refresh Token Technical Profile</DisplayName>
<Protocol Name="None" />
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="objectId" />
</OutputClaims>
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
The second orchestration step invokes the AAD-UserReadUsingObjectId technical profile that reads claims from the Azure AD B2C directory for the signed-in user by the objectId claim.
Another orchestration step can call your REST API.
The final orchestration step issues new tokens.
You must reference the TokenRefresh user journey using the RefreshTokenUserJourneyId metadata item with the JwtIssuer technical profile so that tokens that are issued by this technical profile are refreshed by this user journey:
<ClaimsProvider>
<DisplayName>Token Issuer</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="JwtIssuer">
<DisplayName>JWT Issuer</DisplayName>
<Protocol Name="None" />
<OutputTokenFormat>JWT</OutputTokenFormat>
<Metadata>
<Item Key="client_id">{service:te}</Item>
<Item Key="issuer_refresh_token_user_identity_claim_type">objectId</Item>
<Item Key="RefreshTokenUserJourneyId">TokenRefresh</Item>
<Item Key="SendTokenResponseBodyWithJsonNumbers">true</Item>
</Metadata>
<CryptographicKeys>
<Key Id="issuer_secret" StorageReferenceId="B2C_1A_TokenSigningKeyContainer" />
<Key Id="issuer_refresh_token_key" StorageReferenceId="B2C_1A_TokenEncryptionKeyContainer" />
</CryptographicKeys>
<InputClaims />
<OutputClaims />
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>

Related

Azure B2C: REST call with external IDP

After configuring an external IDP in a user journey a subsequent REST call has to be made by sending an identifier to the REST endpoint. Upon receiving the response from the REST api, an attribute from the response has to be populated in the idtoken that has to be sent back to the caller.
The custom policy looks like this:
<ClaimsProvider>
<Domain>live.com</Domain>
<DisplayName>Microsoft Account</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="MSA-OIDC">
<DisplayName>Microsoft Account</DisplayName>
<Protocol Name="OpenIdConnect"/>
<Metadata>
<Item Key="ProviderName">https://login.live.com</Item>
<Item Key="METADATA">https://login.live.com/.well-known/openid-configuration</Item>
<Item Key="response_types">code</Item>
<Item Key="response_mode">form_post</Item>
<Item Key="scope">openid profile email</Item>
<Item Key="HttpBinding">POST</Item>
<Item Key="UsePolicyInRedirectUri">0</Item>
<Item Key="client_id">xxxxxxxxxx</Item>
</Metadata>
<CryptographicKeys>
<Key Id="client_secret" StorageReferenceId="B2C_1A_MSASecret"/>
</CryptographicKeys>
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
The REST call is like this:
curl --location --request GET 'https://swapi.dev/api/films/1/'
What I am looking for is a way to integrate the REST call with the user journey. Appreciate the responses.
This Azure AD B2C sample demonstrates how to log in to an external identity provider and then invoke a REST API.
An example of your user journey might be:
<UserJourney Id="SignUpOrSignIn">
<OrchestrationSteps>
<OrchestrationStep Order="1" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="MSA-OIDC" TechnicalProfileReferenceId="MSA-OIDC"/>
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="2" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="REST-GetFilm" TechnicalProfileReferenceId="REST-GetFilm"/>
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="3" Type="SendClaims" CpimIssuerTechnicalProfileReferenceId="JwtIssuer" />
</OrchestrationSteps>
</UserJourney>

Azure B2C call azure function with JWT token or code kept as policy key

is it possible to call azure function without hardcoding function code in service url?
For example using policy key and sending it as query string/header. I don't want it to be exposed.
Alternative is to use AAD auth on app service level, but that would require generating JWT token before step "SendClaims" and it could possibly led to authenticated users having access to this function.
Thanks for any help.
Edit:
Ok, I did it as suggested and I got to a point where I have everything set up.
I can request a working token using postman and authorize properly to azure function.
I have debugged said token in user journey by outputting it as user claim (I confirmed that acquire step is working), but I get an error on calling function
AADB2C90027: Basic credentials specified for 'Azure-Functions-Notify-New-User-Registered' are invalid. Check that the credentials are correct and that access has been granted by the resource.
So far my xml file looks like that:
<ClaimsProvider>
<DisplayName>Aquire JWT token to call azure function</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="Azure-Functions-Notify-New-User-Registered-AccessToken">
<DisplayName>Acquire JWT token to call azure function</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://login.microsoftonline.com/{my-tenant}/oauth2/v2.0/token</Item>
<Item Key="AuthenticationType">Basic</Item>
<Item Key="SendClaimsIn">Form</Item>
</Metadata>
<CryptographicKeys>
<Key Id="BasicAuthenticationUsername" StorageReferenceId="B2C_1A_NotifyNewUserRegisteredClientId" />
<Key Id="BasicAuthenticationPassword" StorageReferenceId="B2C_1A_NotifyNewUserRegisteredSecret" />
</CryptographicKeys>
<InputClaims>
<InputClaim ClaimTypeReferenceId="grant_type" DefaultValue="client_credentials" AlwaysUseDefaultValue="true" />
<InputClaim ClaimTypeReferenceId="scope" DefaultValue="https://{my-tenant}/{my-resoruce-id}/.default" AlwaysUseDefaultValue="true" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="bearerToken" PartnerClaimType="access_token" />
</OutputClaims>
<UseTechnicalProfileForSessionManagement ReferenceId="SM-Noop" />
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
<ClaimsProvider>
<DisplayName>Azure-Functions-Notify-New-User-Registered</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="Azure-Functions-Notify-New-User-Registered">
<DisplayName>Call Azure Function when new user registers</DisplayName>
<Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.RestfulProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
<Metadata>
<Item Key="ServiceUrl">{my-azure-function-with-function-code}</Item>
<Item Key="SendClaimsIn">Body</Item>
<Item Key="AuthenticationType">Bearer</Item>
<Item Key="UseClaimAsBearerToken">bearerToken</Item>
<Item Key="AllowInsecureAuthInProduction">false</Item>
</Metadata>
<InputClaims>
<InputClaim ClaimTypeReferenceId="bearerToken"/>
</InputClaims>
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
And my user journey:
<OrchestrationStep Order="7" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="Azure-Functions-Notify-New-User-Registered-AccessToken" TechnicalProfileReferenceId="Azure-Functions-Notify-New-User-Registered-AccessToken" />
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="8" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="Azure-Functions-Notify-New-User-Registered" TechnicalProfileReferenceId="Azure-Functions-Notify-New-User-Registered" />
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="9" Type="SendClaims" CpimIssuerTechnicalProfileReferenceId="JwtIssuer" />
Protect it with AAD. Get the JWT to the function app using this:
https://learn.microsoft.com/en-us/azure/active-directory-b2c/secure-rest-api#oauth2-bearer-authentication
No you don’t need to worry about the user getting this JWT, that’s impossible unless you decide to output this JWT as a claim in the relying party section.

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.

Azure AD B2C Sign Up with Email Invitation Does Not Show Sign Up Page

I am incorporating a Sign Up with Email Invitation flow in my project by following this Azure AD B2C sample from microsoft:
https://github.com/azure-ad-b2c/samples/tree/master/policies/invite
For test reasons I am setting the redirect_uri parameter of the invitation URL to https://jwt.ms and my expectation for the workflow is:
Clicking on the invite URL takes me to b2clogin
Azure B2C validates the ID hint token
I land on the Sign Up page with pre-populated values in the ID token hint
Upon a successful sign up, I am redirected to https://jwt.ms
My expectation, however, is not met and upon clicking the invite URL, I immediately land on the https://jwt.ms with a JWT token containing the invitation number (more details below) and the object ID (sub) of one of the previously created profiles in AD, plus the standard claims like exp, aud, etc.
I suspect that there is a gap in my understanding of how the invite workflow function. What areas of code/policies should I pay attention and modify to ensure a successful invitation sign up?
Some extra details:
I am including an invitation number in the ID token hint and NOT an email, therefore the ReadOnlyEmail is replaced with InvitationNumber throughout the custom policy.
I have copied fields from my normal sign up policy to the invitation policy, expecting that he user should be able to sign up with any emails they like as long as it's validated by B2C (hence the "False" is removed from the sample technical profile for invitation sign up)
The invitation number is also set as an output claim for my app to process it once the JWT token is received from B2C.
The invitation policy uses the same policy base as my normal sign in/up.
In the shared policy base, I have added a new claims provider for ID token hint validation next to my normal JwtIssuer which references the signing certificate that my app uses to sign the ID token hint and use it in the last step of the SignUpInvitation user journey. I am not sure this is the right thing, but once I use the JwtIssuer, I get an error in B2C that it cannot verify the signature of ID token hint.
The technical profile for sign up is as follows, and it is being called from the user journey:
<TechnicalProfile Id="LocalAccountSignUpWithInvitationToken">
<DisplayName>Email signup</DisplayName>
<Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.SelfAssertedAttributeProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
<Metadata>
<Item Key="IpAddressClaimReferenceId">IpAddress</Item>
<Item Key="ContentDefinitionReferenceId">api.localaccountsignup</Item>
<Item Key="language.button_continue">Create</Item>
</Metadata>
<InputClaimsTransformations>
<InputClaimsTransformation ReferenceId="CopyInvitationToken" />
</InputClaimsTransformations>
<InputClaims>
<InputClaim ClaimTypeReferenceId="extension_InvitationToken" />
<InputClaim ClaimTypeReferenceId="email" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="objectId" />
<OutputClaim ClaimTypeReferenceId="email" PartnerClaimType="Verified.Email" Required="true" />
<OutputClaim ClaimTypeReferenceId="newPassword" Required="true" />
<OutputClaim ClaimTypeReferenceId="reenterPassword" Required="true" />
<OutputClaim ClaimTypeReferenceId="executed-SelfAsserted-Input" DefaultValue="true" />
<OutputClaim ClaimTypeReferenceId="authenticationSource" />
<OutputClaim ClaimTypeReferenceId="newUser" />
<!-- Optional claims, to be collected from the user -->
<OutputClaim ClaimTypeReferenceId="givenName" />
<OutputClaim ClaimTypeReferenceId="surName" />
<OutputClaim ClaimTypeReferenceId="extension_InvitationToken" />
</OutputClaims>
<ValidationTechnicalProfiles>
<ValidationTechnicalProfile ReferenceId="AAD-UserWriteUsingLogonEmail" />
</ValidationTechnicalProfiles>
<UseTechnicalProfileForSessionManagement ReferenceId="SM-AAD" />
</TechnicalProfile>
7. The user journey is:
<UserJourneys>
<UserJourney Id="SignUpInvitation">
<OrchestrationSteps>
<!--Read the input claims from the id_token_hint-->
<OrchestrationStep Order="1" Type="GetClaims" CpimIssuerTechnicalProfileReferenceId="IdTokenHint_ExtractClaims" />
<!-- Check if user tries to run the policy without invitation -->
<OrchestrationStep Order="2" Type="ClaimsExchange">
<Preconditions>
<Precondition Type="ClaimsExist" ExecuteActionsIf="true">
<Value>extension_InvitationToken</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
</Preconditions>
<ClaimsExchanges>
<ClaimsExchange Id="SelfAsserted-Unsolicited" TechnicalProfileReferenceId="SelfAsserted-Unsolicited"/>
</ClaimsExchanges>
</OrchestrationStep>
<!-- Self-asserted sign-up page -->
<OrchestrationStep Order="3" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="LocalAccountSignUpWithInvitationToken" TechnicalProfileReferenceId="LocalAccountSignUpWithInvitationToken"/>
</ClaimsExchanges>
</OrchestrationStep>
<!-- Issue an access token-->
<OrchestrationStep Order="4" Type="SendClaims" CpimIssuerTechnicalProfileReferenceId="JwtIdTokenHintValidator"/>
</OrchestrationSteps>
<ClientDefinition ReferenceId="DefaultWeb"/>
</UserJourney>
</UserJourneys>
Change <UseTechnicalProfileForSessionManagement ReferenceId="SM-AAD" /> to <UseTechnicalProfileForSessionManagement ReferenceId="SM-Noop" />. You are skipping the page due to SSO it seems.
https://github.com/azure-ad-b2c/samples/blob/master/policies/invite/policy/SignUpInvitation.xml#L100

Azure B2C: Adding an orchestration step to ask for more information

I'm looking to break up our registration journey into multiple pages, so we don't end up with a massive form.
I'm trying to add an orchestration step after the initial registration page to ask for the user's favourite colour.
I have added the following claims provider:
<ClaimsProvider>
<DisplayName>Self Asserted</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="SATP-GetFavouriteColour">
<DisplayName>Local Account Sign In</DisplayName>
<Protocol Name="Proprietary"
Handler="Web.TPEngine.Providers.SelfAssertedAttributeProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"/>
<Metadata>
<Item Key="ContentDefinitionReferenceId">api.signuporsignin</Item>
</Metadata>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="favouriteColour" Required="true" />
</OutputClaims>
<UseTechnicalProfileForSessionManagement ReferenceId="SM-Noop"/>
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
and have updated my SignUpOrSignIn journey to include it just before the final step of returning the claims to the RP, like so:
<OrchestrationStep Order="8" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="GetFavouriteColour" TechnicalProfileReferenceId="SATP-GetFavouriteColour" />
</ClaimsExchanges>
</OrchestrationStep>
I have also added the output claim to my Relying Party file like so:
<OutputClaim ClaimTypeReferenceId="favouriteColour" DefaultValue="Lemons"/>
The policy files validate and upload successfully, but when I go through the journey, I simply get the default value of "Lemons" returned to my RP.
I expected B2C to ask the user for their favourite colour. Why isn't B2C asking the user for the new field I added?
Am I right in thinking this is possible, and I'm just missing something simple?
Thanks in advance
I found the cause of this, I had missed off <UserInputType>TextBox</UserInputType> from my claimType declaration

Resources