Looking to have Azure B2C custom policy use a custom error page (and pass a value) - azure

UPDATE:
I am able to finally get my custom error page to render. I had to use
<div id="api"></div>
in my view.
My last concern here, which seems impossible, is adding the value of one of my claims (from the idToken passed to my B2C policy when I call it) into the LoadUri of my ContentDefintion.
Here are my TechnicalProfiles:
<TechnicalProfile Id="SelfAsserted-SigningNotReadyError">
<InputClaimsTransformations>
<InputClaimsTransformation ReferenceId="GetUser" />
</InputClaimsTransformations>
<IncludeTechnicalProfile ReferenceId="SelfAsserted-UserError" />
</TechnicalProfile>
<TechnicalProfile Id="SelfAsserted-UserError">
<DisplayName>Unsolicited error message</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.signingerrorlink</Item>
</Metadata>
<InputClaims>
<InputClaim ClaimTypeReferenceId="errorMessage" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="errorMessage" />
</OutputClaims>
</TechnicalProfile>
and my ContentDefinition
Note: see how I have to hardcode a GUID. :(
signingerror?user=7ec00437-1ad9-4acc-a285-a729003ca99d
<ContentDefinition Id="api.signingerrorlink">
<LoadUri>https://something.azurewebsites.net/signingerror?user=7ec00437-1ad9-4acc-a285-a729003ca99d</LoadUri>
<RecoveryUri>https://something.azurewebsites.net/error</RecoveryUri>
<DataUri>urn:com:microsoft:aad:b2c:elements:contract:selfasserted:1.2.0</DataUri>
<Metadata>
<Item Key="DisplayName">Signing Not Ready</Item>
</Metadata>
</ContentDefinition>
ORIGINAL:
I'm in a pickle here. I have been looking through many posts in stackoverflow and over the internet in general. I hope you can help me.
I have a custom B2C policy, which basically accepts a token, performs some functions then returns an access token in the end. That works fine. My issue is that I need to define a step which will trigger a techical profile based on a precondition. The precondition actually works well to.
What I am missing is a way to construct a technicalprofile/contentdefinition to load my own error page (.Net Core 3 controller) in another project but totally accessible (Controllers/SigningErrorController).
My step:
<OrchestrationStep Order="7" Type="ClaimsExchange">
<Preconditions>
<Precondition Type="ClaimEquals" ExecuteActionsIf="true">
<Value>isReadyForSigning</Value>
<Value>True</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
</Preconditions>
<ClaimsExchanges>
<ClaimsExchange Id="SelfAsserted-SigningNotReady" TechnicalProfileReferenceId="SelfAsserted-SigningNotReadyError" />
</ClaimsExchanges>
</OrchestrationStep>
Part 2! I need to pass a value to this custom error page. A guid in this case.
If I use the TP for invalidlink then this works. I have tried various custom contentdefinitions similar to:
<ContentDefinition Id="api.signingerrorlink">
<LoadUri>https://something.azurewebsites.net/error</LoadUri>
<RecoveryUri>https://something.azurewebsites.net/signingerror</RecoveryUri>
<DataUri>urn:com:microsoft:aad:b2c:elements:globalexception:1.1.0</DataUri>
<Metadata>
<Item Key="DisplayName">Error page</Item>
</Metadata>
</ContentDefinition>
...with the signingerror being the custom error page I want to go to.
If I run this, the policy just skips over the step completely. If I use the invalid link TP, then it works. I've tried to figure out also how to pass a value to the error page. I'm at a loss folks.
Any help you can give would be much appreciated.

You can use a claim resolver in the LoadUri element. E.g.:
<ContentDefinition Id="api.signingerrorlink">
<LoadUri>https://something.azurewebsites.net/signingerror?user={Claim:packageUserId}</LoadUri>
<RecoveryUri>https://something.azurewebsites.net/error</RecoveryUri>
<DataUri>urn:com:microsoft:aad:b2c:elements:contract:selfasserted:1.2.0</DataUri>
<Metadata>
<Item Key="DisplayName">Signing Not Ready</Item>
</Metadata>
</ContentDefinition>
https://learn.microsoft.com/en-us/azure/active-directory-b2c/claim-resolver-overview#content-definition

Related

Technical Profile fails to match user account with email (signInNames.emailAddress) claim

Can anyone explain to me (before Azure B2C Custom Policies make me pull what's left of my hair out), why this technical profile fails to ever return an "objectId" when a user account exists in Azure B2C. I am collecting the email claim in a previous screen and calling the technical profile from the orchestration step.
I can see the profile executing in my Application Insights logs and I have confirmed that the email address I use in the claim is in the directory. But every time, no matter which email address I use, I never get an objectId back which means I can never detect if the user exists or not!
Technical Profile
<TechnicalProfile Id="UE-AAD-CheckAccountExistsByEmail">
<Protocol Name="Proprietary"
Handler="Web.TPEngine.Providers.ClaimsTransformationProtocolProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
<Metadata>
<Item Key="Operation">Read</Item>
<Item Key="RaiseErrorIfClaimsPrincipalDoesNotExist">false</Item>
<Item Key="UserMessageIfClaimsPrincipalDoesNotExist">An account could not be found for the provided email address.</Item>
</Metadata>
<IncludeInSso>false</IncludeInSso>
<InputClaims>
<InputClaim ClaimTypeReferenceId="email" PartnerClaimType="signInNames.emailAddress" Required="true"/>
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="objectId"/>
</OutputClaims>
<IncludeTechnicalProfile ReferenceId="AAD-Common" />
</TechnicalProfile>
Orchestration Step
<OrchestrationStep Order="2" Type="ClaimsExchange">
<Preconditions>
<!-- Skip this if we already have an object id from single signon -->
<Precondition Type="ClaimsExist" ExecuteActionsIf="true">
<Value>objectIdFromSession</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
</Preconditions>
<ClaimsExchanges>
<!-- Call a technical profile to see if an account can be found with the email supplied in AD -->
<ClaimsExchange Id="AccountExistsClaim"
TechnicalProfileReferenceId="UE-AAD-CheckAccountExistsByEmail" />
</ClaimsExchanges>
</OrchestrationStep>
You have specified ClaimsTransformationProtocolProvider as the handler.
You need the AAD provider as the handler to make Graph API queries.
Though if AAD-Common already has the Protocol element, you don't need to specify it here again since it'll be included from there.
Like so:
<TechnicalProfile Id="UE-AAD-CheckAccountExistsByEmail">
<!-- You don't actually need this though if AAD-Common has it -->
<Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.AzureActiveDirectoryProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
<Metadata>
<Item Key="Operation">Read</Item>
<Item Key="RaiseErrorIfClaimsPrincipalDoesNotExist">false</Item>
<Item Key="UserMessageIfClaimsPrincipalDoesNotExist">An account could not be found for the provided email address.</Item>
</Metadata>
<IncludeInSso>false</IncludeInSso>
<InputClaims>
<InputClaim ClaimTypeReferenceId="email" PartnerClaimType="signInNames.emailAddress" Required="true"/>
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="objectId"/>
</OutputClaims>
<IncludeTechnicalProfile ReferenceId="AAD-Common" />
</TechnicalProfile>
ClaimsTransformationProtocolProvider is used for running claims transformations to produce new claims or modify existing claims.
It is not used for querying AAD.

Azure B2 - jit migration failing to authenticate user on password reset - all configured using custom policies

So I have been involved in implementing Azure B2C using custom policies.
I've tried to follow jit v2 migration process from this git repo https://github.com/azure-ad-b2c/user-migration/tree/master/jit-migration-v2
So the legacy system is .net framework 4.6.2 and MS SQL database. I have managed to implement all functionalities needed for B2C with the help of owin libraries.
So B2C is supposed to redirect users back to our website after the forgot password process and it does redirect but the user is not authenticated. Instead, we’re getting this generic error:
"OpenIdConnectMessage.Error was not null, indicating an error. Error: 'server_error'. Error_Description (may be empty): 'AADB2C90037: An error occurred while processing the request. Please contact administrator of the site you are trying to access.
Correlation ID: d38ccb08-4c77-4716-97e0-465f0aebfeb6
Timestamp: 2021-08-09 08:34:22Z
'. Error_Uri (may be empty): 'error_uri is null'."
I need to say that forgot password process with authenticated redirect back works all fine when the user is already migrated to B2C. The problem only happens when the user is a non-migrated user. Both scenarios are using the same custom policies except some ValidationTechnicalProfile are conditional on whether the user has been migrated or not.
Here is piece of our custom policy that is responsible for password reset process:
<!-- PASSWORD RESET first page -->
<TechnicalProfile Id="PasswordResetFirstPage">
<DisplayName>Reset password using email address</DisplayName>
<Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.SelfAssertedAttributeProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
<Metadata>
<Item Key="EnforceEmailVerification">True</Item>
<Item Key="IpAddressClaimReferenceId">IpAddress</Item>
<Item Key="ContentDefinitionReferenceId">api.localaccountpasswordreset</Item>
<Item Key="UserMessageIfClaimsTransformationBooleanValueIsNotEqual">Your account has been locked. Contact your support person to unlock it, then try again.</Item>
</Metadata>
<CryptographicKeys>
<Key Id="issuer_secret" StorageReferenceId="B2C_1A_TokenSigningKeyContainer" />
</CryptographicKeys>
<IncludeInSso>false</IncludeInSso>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="email" PartnerClaimType="Verified.Email" Required="true" />
<OutputClaim ClaimTypeReferenceId="objectId" />
<OutputClaim ClaimTypeReferenceId="userPrincipalName" />
<OutputClaim ClaimTypeReferenceId="authenticationSource" />
</OutputClaims>
<!-- NOTE: Remove the validation technical profile to the extension policy -->
<ValidationTechnicalProfiles>
<ValidationTechnicalProfile ReferenceId="AAD-UserReadUsingEmailAddress" ContinueOnError="true" />
<ValidationTechnicalProfile ReferenceId="REST-UserMigration-LocalAccount-PasswordReset1" ContinueOnError="false" />
</ValidationTechnicalProfiles>
</TechnicalProfile>
<!-- PASSWORD RESET second page -->
<TechnicalProfile Id="PasswordResetSecondPage">
<DisplayName>Change password (username)</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.localaccountpasswordreset</Item>
</Metadata>
<CryptographicKeys>
<Key Id="issuer_secret" StorageReferenceId="B2C_1A_TokenSigningKeyContainer" />
</CryptographicKeys>
<InputClaims>
<InputClaim ClaimTypeReferenceId="objectId" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="newPassword" Required="true" />
<OutputClaim ClaimTypeReferenceId="reenterPassword" Required="true" />
</OutputClaims>
<!-- NOTE: Remove the validation technical profile to the extension policy -->
<ValidationTechnicalProfiles>
<ValidationTechnicalProfile ReferenceId="AAD-UserWritePasswordUsingObjectId">
<!--Don't run this validation technical profile if objectId is not exists (migrated acccount)-->
<Preconditions>
<Precondition Type="ClaimsExist" ExecuteActionsIf="false">
<Value>objectId</Value>
<Action>SkipThisValidationTechnicalProfile</Action>
</Precondition>
</Preconditions>
</ValidationTechnicalProfile>
<ValidationTechnicalProfile ReferenceId="REST-UserMigration-LocalAccount-PasswordReset2">
<!--Don't run this validation technical profile if objectId is exists (existing acccount)-->
<Preconditions>
<Precondition Type="ClaimsExist" ExecuteActionsIf="true">
<Value>objectId</Value>
<Action>SkipThisValidationTechnicalProfile</Action>
</Precondition>
</Preconditions>
</ValidationTechnicalProfile>
<ValidationTechnicalProfile ReferenceId="AAD-MigrateUserUsingLogonEmail">
<!--Don't run this validation technical profile if objectId is exists (existing acccount)-->
<Preconditions>
<Precondition Type="ClaimsExist" ExecuteActionsIf="true">
<Value>objectId</Value>
<Action>SkipThisValidationTechnicalProfile</Action>
</Precondition>
</Preconditions>
</ValidationTechnicalProfile>
<ValidationTechnicalProfile ReferenceId="REST-UserMigration-PasswordUpdate" ContinueOnError="false" />
</ValidationTechnicalProfiles>
</TechnicalProfile>
Generally the whole process works fine: user is migrated to B2C and password is reset but the actual redirect back to our website is just not authenticated. So far the only workaround we have managed to implement is to just redirect user back to login page but it would be nice to understand what's going on and fix it.
I also need to mention that the same migration step works fine for non-migrated users that want to sign in and there is no problem with that scenario.
We have contacted azure support and we already had few calls with them but it looks like they don't know why this happens and they have not been able to help us sort this out.
Any suggestions very appriciated.

Azure B2C custom policy - Is there a way to display a claim in a ClaimsProviderSelection orchestration step

I am currently working with Azure B2C custom policies for my Auth flow.
I have a ClaimsProviderSelection orchestration step which shows the user two options:
Send code to their MFA email saved in authentication methods
Lost Email
What I would like to do is show the users email address through the use of a ClaimProvider in either the display text, or the button itself (see below)
If this is not possible, then I would love to be able to add a 'lost email' button on the verification control page itself - like so:
From what I have seen though, it seems this is only available with 'ForgotPasswordExchange' (as seen here: https://learn.microsoft.com/en-us/azure/active-directory-b2c/add-password-reset-policy?pivots=b2c-custom-policy) for passwords and not authentication methods.
If anyone has any experience with customizing ClaimsProviderSelection steps, or adding custom links on orchestration steps your help would be greatly appreciated!
See below for code examples:
Orchestration step:
<OrchestrationStep Order="2" Type="ClaimsProviderSelection" ContentDefinitionReferenceId='api.MFAselections' >
<Preconditions>
<Precondition Type="ClaimsExist" ExecuteActionsIf="false">
<Value>strongAuthenticationEmailAddress</Value>
<Value>strongAuthenticationPhoneNumber</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
</Preconditions>
<ClaimsProviderSelections>
<ClaimsProviderSelection TargetClaimsExchangeId="MFAVerifyEmailAddress" />
<ClaimsProviderSelection TargetClaimsExchangeId="LostEmailExchange" />
</ClaimsProviderSelections>
</OrchestrationStep>
Technical Profile:
<TechnicalProfile Id="MFA_VerifyEmailAddress">
<DisplayName>SEND TO {Claim:strongAuthenticationEmailAddress}
</DisplayName>
<Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.SelfAssertedAttributeProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
<Metadata>
<Item Key="ContentDefinitionReferenceId">MFAVerifyEmail</Item>
<Item Key="IncludeClaimResolvingInClaimsHandling">true</Item>
<!-- <Item Key="setting.showContinueButton">false</Item> -->
<Item Key="setting.showCancelButton">false</Item>
<Item Key="UserMessageIfSessionDoesNotExist">You have exceeded the maximum time allowed.</Item>
<Item Key="UserMessageIfMaxRetryAttempted">You have exceeded the number of retries allowed.</Item>
<Item Key="UserMessageIfInvalidCode">You have entered the wrong code.</Item>
<Item Key="UserMessageIfSessionConflict">Cannot verify the code, please try again later.</Item>
</Metadata>
<InputClaims>
<InputClaim ClaimTypeReferenceId="MFAcomplete" DefaultValue="true" AlwaysUseDefaultValue='true'/>
</InputClaims>
<DisplayClaims>
<DisplayClaim DisplayControlReferenceId="emailVerificationControl" />
</DisplayClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="MFAcomplete" DefaultValue="email" AlwaysUseDefaultValue='true' />
<OutputClaim ClaimTypeReferenceId="isLostEmail" DefaultValue="false" AlwaysUseDefaultValue='true' />
</OutputClaims>
<UseTechnicalProfileForSessionManagement ReferenceId="SM-AAD" />
</TechnicalProfile>
For anyone who is coming across this - this is what I ended up doing:
Add ContentDefinitionParameters with claim to UserJourneyBehaviors in your RelyingParty
<ContentDefinitionParameters> <Parameter Name="email">{Claim:maskedEmail}</Parameter> </ContentDefinitionParameters>
Use JS to grab email claim from source code, and insert to HTML
const parser = new URL(SETTINGS.remoteResource); let email = parser.searchParams.get('email');
Have you tried to do Output Claims transformation on the email, create a claim of type string, then append the email to it, in a previous step. And display that on the screen.

Force user to login after ADB2C signup

When the user signup on the adb2c, I want him to type his login / password, and not being already connected.
I tried to edit the signin_signup policy but without results
In Custom Policies, you can use a self asserted page to show the user that they have successfully signed up, but not allow them to continue the journey from there. This is sometimes referred to as a "block page" in our samples. Since the user cannot continue the journey, a token will not be issued and a session will not be established.
You can instead use a Custom HTML page to allow the user to return to the home page from here.
The user then needs to login again to get an authenticated session.
The block page is shown to be used here:
https://github.com/azure-ad-b2c/samples/blob/4e43fab365e29f002e9e033a4e078bc2091a8494/policies/password-reset-only/policy/TrustFrameworkExtensions.xml#L132
<TechnicalProfile Id="SelfAsserted-BlockSignUpClaimsIssuance">
<DisplayName>BlockSignUpClaimsIssuance Page</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.selfasserted.blockSignUpPage</Item>
<!--Demo: hide the continue and cancel buttons -->
<Item Key="setting.showContinueButton">false</Item>
<Item Key="setting.showCancelButton">false</Item>
</Metadata>
<InputClaimsTransformations>
<InputClaimsTransformation ReferenceId="GenerateAMessage" />
</InputClaimsTransformations>
<InputClaims>
<InputClaim ClaimTypeReferenceId="userMessage" />
</InputClaims>
<OutputClaims>
<!--Demo: Show the paragraph claim with the message to the user -->
<OutputClaim ClaimTypeReferenceId="userMessage" />
</OutputClaims>
<UseTechnicalProfileForSessionManagement ReferenceId="SM-Noop" />
</TechnicalProfile>
Apply a precondition to this step, such that if the newUser claim exists, that this step is not skipped:
<OrchestrationStep Order="YOUR STEP #" Type="ClaimsExchange">
<Preconditions>
<Precondition Type="ClaimsExist" ExecuteActionsIf="false">
<Value>newUser</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
</Preconditions>
<ClaimsExchanges>
<ClaimsExchange Id="SelfAsserted-BlockSignUpClaimsIssuance" />
</ClaimsExchanges>
</OrchestrationStep>

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.

Resources