ADB2C Custom Policy - validate a claim against a string value always returns true - azure-active-directory

I am having an id_token_hint from which I am reading an extension attribute to check if the role is Admin and if so not to issue (skip) token.
To check if the user is Admin I am doing the following
<ClaimType Id="isAdmin">
<DisplayName>Verify if user is Admin User</DisplayName>
<DataType>boolean</DataType>
<UserHelpText>Verify if user is Admin User</UserHelpText>
</ClaimType>
Claim Transformation
<ClaimsTransformation Id="isAdminUser" TransformationMethod="CompareClaimToValue">
<InputClaims>
<InputClaim ClaimTypeReferenceId="extension_role" TransformationClaimType="inputClaim1" />
</InputClaims>
<InputParameters>
<InputParameter Id="compareTo" DataType="string" Value="Super Admin" />
<InputParameter Id="operator" DataType="string" Value="equal" />
<InputParameter Id="ignoreCase" DataType="string" Value="true" />
</InputParameters>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="isAdmin" TransformationClaimType="outputClaim" />
</OutputClaims>
</ClaimsTransformation>
UserJourney
<OrchestrationStep Order="5" Type="ClaimsExchange">
<Preconditions>
<Precondition Type="ClaimEquals" ExecuteActionsIf="true">
<Value>isAdminUser</Value>
<Value>False</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
</Preconditions>
<ClaimsExchanges>
<ClaimsExchange Id="AdminUserNotAllowed" TechnicalProfileReferenceId="AdminUserNotAllowed" />
</ClaimsExchanges>
</OrchestrationStep>
The issue is that this condition returns true for all users (non-Admin aswell) .
From App insights :
Claims
email: myadmin#mailinator.com
errorMessage: Admin user access via SignIn link is not allowed. Please access the application via normal SignIn
extension_role: Super Admin
Claims
email: myorgadmin#abc.com
errorMessage: Admin user access via SignIn link is not allowed. Please access the application via normal SignIn
extension_role: Organization Admin RW
To clarify my requirement is
if (extension_role === "Super Admin")
//Do not allow magic signIn
I have tried initially ClaimEquals but that had also returned true in all cases.
<OrchestrationStep Order="6" Type="ClaimsExchange">
<Preconditions>
<Precondition Type="ClaimEquals" ExecuteActionsIf="tru">
<Value>extension_role</Value>
<Value>Super Admin</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
</Preconditions>
<ClaimsExchanges>
<ClaimsExchange Id="SelfAssertedAgencyNotMatched" TechnicalProfileReferenceId="SelfAssertedAgencyNotMatched" />
</ClaimsExchanges>
</OrchestrationStep>

Try something like this.
This uses TransformationMethod="CompareClaims",

Related

OIDC-Connect technical profile only works under a self-asserted validation reference (but need it not to require a self-asserted profile)

I am hoping to create an endpoint that allows me to pass in a password as a query param (for the purpose of issuing a JWT for internal M2M usage between microservices. The password is only aimed at preventing services who should be able to have the M2M rather than being super secure as such etc.
I am stuck however with a bug or feature of b2c where I can call the login-NonInteractive profile but it only works if being called from a self-asserted technical profile via a ValidationTechnicalProfile. See working code below (but has a UI because its self asserted):
<TechnicalProfile Id="SelfAsserted-LocalAccountSignin-Username">
<DisplayName>Local Account Signin</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</Item>
<Item Key="UserMessageIfClaimsTransformationStringsAreNotEqual">The last names you provided are not the same</Item>
<Item Key="AllowGenerationOfClaimsWithNullValues">true</Item>
</Metadata>
<IncludeInSso>false</IncludeInSso>
<!-- <InputClaims>
<InputClaim ClaimTypeReferenceId="signInName" />
</InputClaims> -->
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="signInName" Required="true" />
<OutputClaim ClaimTypeReferenceId="password" Required="true" />
<OutputClaim ClaimTypeReferenceId="objectId" />
<OutputClaim ClaimTypeReferenceId="authenticationSource" />
</OutputClaims>
<ValidationTechnicalProfiles>
<ValidationTechnicalProfile ReferenceId="login-NonInteractive" />
</ValidationTechnicalProfiles>
<UseTechnicalProfileForSessionManagement ReferenceId="SM-AAD" />
</TechnicalProfile>
When I directly call login-NonInteractive (from an orchestration step; to skip the UI that is shown the a self-asserted step), I get an error indicating that the request it sends is sent as a GET HTTP verb; but it only accepts OPTIONS and POST verbs. It seems like the POST metadata key is being ignored in this case. Below is that metadata key:
<Item Key="HttpBinding">POST</Item>
This works as said above when doing via a ValidationTechnicalProfile but not when direct called via an orchestration step.
My question is:
Is there any work around to get login-NonInteractive to POST as it should (without requiring a self-asserted technical profile)?
If not; how would I go about using REST technical profiles (or an OAuth2 profile) to achieve the same thing? If I understand the ODIC calls that would be made; I can probably work through this myself I think. I read the docs here: https://learn.microsoft.com/en-us/azure/active-directory-b2c/openid-connect-technical-profile and here: https://learn.microsoft.com/en-us/azure/active-directory/develop/v2-protocols-oidc but the last link doesn't feel like it applies; as I am trying to login as a particular user (for the purpose of validating the password mainly). That approach doesn't cover that specifically. I would also prefer to avoid ROPC if possible since its deprecated.
Here is the code for the login-NonInteractive:
<TechnicalProfile Id="login-NonInteractive">
<DisplayName>Local Account SignIn</DisplayName>
<Protocol Name="OpenIdConnect" />
<Metadata>
<Item Key="ProviderName">https://sts.windows.net/</Item>
<Item Key="METADATA">https://login.microsoftonline.com/{tenant}/.well-known/openid-configuration</Item>
<Item Key="authorization_endpoint">https://login.microsoftonline.com/{tenant}/oauth2/token</Item>
<Item Key="response_types">id_token</Item>
<Item Key="response_mode">query</Item>
<Item Key="scope">email openid</Item>
<!-- <Item Key="grant_type">password</Item> -->
<!-- Policy Engine Clients -->
<Item Key="UsePolicyInRedirectUri">false</Item>
<Item Key="HttpBinding">POST</Item>
</Metadata>
<InputClaims>
<InputClaim ClaimTypeReferenceId="signInName" PartnerClaimType="username" Required="true" />
<InputClaim ClaimTypeReferenceId="password" Required="true" />
<InputClaim ClaimTypeReferenceId="grant_type" DefaultValue="password" AlwaysUseDefaultValue="true" />
<InputClaim ClaimTypeReferenceId="scope" DefaultValue="openid" AlwaysUseDefaultValue="true" />
<InputClaim ClaimTypeReferenceId="nca" PartnerClaimType="nca" DefaultValue="1" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="objectId" PartnerClaimType="oid" />
<OutputClaim ClaimTypeReferenceId="tenantId" PartnerClaimType="tid" />
<OutputClaim ClaimTypeReferenceId="givenName" PartnerClaimType="given_name" />
<OutputClaim ClaimTypeReferenceId="surName" PartnerClaimType="family_name" />
<OutputClaim ClaimTypeReferenceId="displayName" PartnerClaimType="name" />
<OutputClaim ClaimTypeReferenceId="userPrincipalName" PartnerClaimType="upn" />
<OutputClaim ClaimTypeReferenceId="authenticationSource" DefaultValue="localAccountAuthentication" />
</OutputClaims>
</TechnicalProfile>
```
I have worked out what these guides (https://learn.microsoft.com/en-us/azure/active-directory-b2c/client-credentials-grant-flow?pivots=b2c-custom-policy) etc are advocating now so am posting my answer here.
My confusion came from:
The client credentials are calling into the policy directly in the guides example. I had wrongly assumed that a JWT was being generically issued and then later passed into the custom policy in a second step (ie with similar url format that is used in the portal when hitting an endpoint directly). This confusion was probably fostered too by the fact my custom policy didn't work in a way that works with client credentials. It worked when calling it via the portal; but didn't when using client credentials with it strangely. To solve this I recommend just having a basic replying party JWT issuer and not much else (the code sample here helps somewhat but isn't in a working state as such https://github.com/azure-ad-b2c/samples/tree/master/policies/client_credentials_flow):
<RelyingParty>
<DefaultUserJourney ReferenceId="ClientJourney" />
<TechnicalProfile Id="PolicyProfile">
<DisplayName>PolicyProfile</DisplayName>
<Protocol Name="OpenIdConnect" />
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="objectId" PartnerClaimType="sub" AlwaysUseDefaultValue="true" DefaultValue="myApp" />
<OutputClaim ClaimTypeReferenceId="issuingChannel" AlwaysUseDefaultValue="true" DefaultValue="myAdditionalProperty" />
</OutputClaims>
<SubjectNamingInfo ClaimType="sub" />
</TechnicalProfile>
</RelyingParty>
Essentially the flow is that the client_secret etc being used on the {policy}/oauth2/v2.0/token in step 3 of the first link where you perform the following action:
curl --location --request POST 'https://<your-tenant>.b2clogin.com/<your-tenant>.onmicrosoft.com/<policy>/oauth2/v2.0/token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--form 'grant_type="client_credentials"' \
--form 'client_id="<client ID>"' \
--form 'client_secret="<client secret>"' \
--form 'scope="<Your API id uri>/.default"'
is hitting the policy directly (where as through the portal you use endpoints to hit your policy like this: https://{tenantName}.b2clogin.com/{tenantName}.onmicrosoft.com/oauth2/v2.0/authorize which had me thinking that I needed two calls when I only needed the one in the example.

Azure AD B2C Custom Policy Verify Code & Continue in same Action

I have a existing user flow which is working as expected with verify code and continue button action. Currently the issue is user has to click so many action buttons to login if MFA is enabled. So the expected user flow should skip or bypass the step of continue where verify action button will handle both validation of otp as well as continue user flow in single click. I am trying to combine step to verify code and continue button in one action. Any kind of help is appreciated and thanks in advance. Attaching image for better understanding too. Below is my code
<DisplayControls>
<DisplayControl Id="emailVerificationControl" UserInterfaceControlType="VerificationControl">
<DisplayClaims>
<DisplayClaim ClaimTypeReferenceId="email" Required="true" />
<DisplayClaim ClaimTypeReferenceId="verificationCode" ControlClaimType="VerificationCode" Required="true" />
</DisplayClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="email" />
</OutputClaims>
<Actions>
<Action Id="SendCode">
<ValidationClaimsExchange>
<ValidationClaimsExchangeTechnicalProfile TechnicalProfileReferenceId="SelfAsserted-GenerateOtp" />
<ValidationClaimsExchangeTechnicalProfile TechnicalProfileReferenceId="SendGridSendOtp" />
</ValidationClaimsExchange>
</Action>
<Action Id="VerifyCode">
<ValidationClaimsExchange>
<ValidationClaimsExchangeTechnicalProfile TechnicalProfileReferenceId="SelfAsserted-VerifyOtp" />
</ValidationClaimsExchange>
</Action>
</Actions>
</DisplayControl>
<ClaimsProvider>
<DisplayName>SelfAsserted-VerifyOtp</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="SelfAsserted-VerifyOtp">
<DisplayName>Verify one time password</DisplayName>
<Protocol
Name="Proprietary"
Handler="Web.TPEngine.Providers.OneTimePasswordProtocolProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"
/>
<Metadata>
<Item Key="Operation">VerifyCode</Item>
</Metadata>
<InputClaims>
<InputClaim ClaimTypeReferenceId="email" PartnerClaimType="identifier" />
<InputClaim ClaimTypeReferenceId="verificationCode" PartnerClaimType="otpToVerify" />
</InputClaims>
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
<ClaimsProvider>
<DisplayName>SelfAsserted-GenerateOtp</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="SelfAsserted-GenerateOtp">
<DisplayName>Generate one time password</DisplayName>
<Protocol
Name="Proprietary"
Handler="Web.TPEngine.Providers.OneTimePasswordProtocolProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"
/>
<Metadata>
<Item Key="Operation">GenerateCode</Item>
<Item Key="CodeExpirationInSeconds">1200</Item>
<Item Key="CodeLength">6</Item>
<Item Key="CharacterSet">0-9</Item>
<Item Key="ReuseSameCode">true</Item>
<Item Key="NumRetryAttempts">5</Item>
</Metadata>
<InputClaims>
<InputClaim ClaimTypeReferenceId="email" PartnerClaimType="identifier" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="otp" PartnerClaimType="otpGenerated" />
</OutputClaims>
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
Existing User Flow
Expected User Flow
You cannot merge the Verify code and Continue buttons in one button using Custom Policy. You need to work with a front-end developer to use a custom HTML page for your signup/sign-in with JavaScript/CSS for this purpose.
You may consider using JavaScript MutationObserver to detect that the Continue button has been enabled, then automate the clicks with javascript

Saving custom defined User attributes with azure AD B2C custom policy

we are trying to set a custom user attribute
we have managed to show to define it the TrustFrameworkExtensions.xml ClaimsSchema
<ClaimType Id="extension_GDPR_CONSENT">
<DisplayName>extension_GDPR_CONSENT</DisplayName>
<DataType>string</DataType>
<UserInputType>CheckboxMultiSelect</UserInputType>
<Restriction>
<Enumeration Text="Accept" Value="true" SelectByDefault="false" />
</Restriction>
</ClaimType>
and also we menaged to show in our signup form
<DisplayName>Local Account</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="LocalAccountSignUpWithLogonEmail">
<DisplayClaims>
<DisplayClaim DisplayControlReferenceId="emailVerificationControl"/>
<!--DisplayClaim ClaimTypeReferenceId="displayName" Required="true" />
<DisplayClaim ClaimTypeReferenceId="givenName" Required="true" />
<DisplayClaim ClaimTypeReferenceId="surName" Required="true" /-->
<DisplayClaim ClaimTypeReferenceId="newPassword" Required="true" />
<DisplayClaim ClaimTypeReferenceId="reenterPassword" Required="true" />
<DisplayClaim ClaimTypeReferenceId="extension_GDPR_CONSENT" Required="true" />
We have also updated the
<TechnicalProfile Id="AAD-UserWriteUsingLogonEmail">
<Metadata>
<Item Key="Operation">Write</Item>
<Item Key="RaiseErrorIfClaimsPrincipalAlreadyExists">true</Item>
</Metadata>
<IncludeInSso>false</IncludeInSso>
<InputClaims>
<InputClaim ClaimTypeReferenceId="email" PartnerClaimType="signInNames.emailAddress" Required="true" />
</InputClaims>
<PersistedClaims>
<!-- Required claims -->
<PersistedClaim ClaimTypeReferenceId="email" PartnerClaimType="signInNames.emailAddress" />
<PersistedClaim ClaimTypeReferenceId="newPassword" PartnerClaimType="password"/>
<PersistedClaim ClaimTypeReferenceId="displayName" DefaultValue="TestCustomPolicy" />
<PersistedClaim ClaimTypeReferenceId="passwordPolicies" DefaultValue="DisablePasswordExpiration" />
<PersistedClaim ClaimTypeReferenceId="extension_GDPR_CONSENT"/>
Adding the directory extension to the persistent claim
But it gives us a validation error?
Regards
To enable extension attributes in the custom policy, provide Application ID and Application Object ID in the AAD-Common technical profile metadata.
<ClaimsProvider>
<DisplayName>Azure Active Directory</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="AAD-Common">
<Metadata>
<!--Insert b2c-extensions-app application ID here, for example: 11111111-1111-1111-1111-111111111111-->
<Item Key="ClientId"></Item>
<!--Insert b2c-extensions-app application ObjectId here, for example: 22222222-2222-2222-2222-222222222222-->
<Item Key="ApplicationObjectId"></Item>
</Metadata>
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
Check Out the link to know more : https://learn.microsoft.com/en-us/azure/active-directory-b2c/user-flow-custom-attributes?pivots=b2c-custom-policy

Azure AD B2C Serialize StringCollection claim in the cookie

I have the following setup for my B2C custom policy:
<TechnicalProfile Id="SM-AAD">
<DisplayName>Session Mananagement Provider</DisplayName>
<Protocol Name="Proprietary" Handler="Web.TPEngine.SSO.DefaultSSOSessionProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
<PersistedClaims>
<PersistedClaim ClaimTypeReferenceId="objectId" />
<PersistedClaim ClaimTypeReferenceId="signInName" />
<PersistedClaim ClaimTypeReferenceId="email" />
<PersistedClaim ClaimTypeReferenceId="groups" />
<PersistedClaim ClaimTypeReferenceId="executed-SelfAsserted-Input" />
</PersistedClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="objectIdFromSession" DefaultValue="true"/>
</OutputClaims>
</TechnicalProfile>
Notice one of the persisted claim is of StringCollection <PersistedClaim ClaimTypeReferenceId="groups" /> type. The original values are like:
"groups": ["guid1", "guid2", "guid3", ...]
All the claims are persisted fine in the session (cookie) except the groups claim. Instead of properly serializing/deserializing the values, on the next token request, we've gotten the following in the JWT instead:
"groups": ["System.Collections.Generic.List`1[System.String]"],
Is there a way to properly serialize StringCollection claim type?

Assign cryptographic key value to claim in azure ad b2c

I have a technical profile to retrieve client credential flow access token from AD token end point.
I am able to assign the response access_token to claim and pass to UI through output claim (once it loaded i will hide the element and change element tpe to hidden) which will be used by JS to make certain api calls in sign up page.
Everything works fine. Here in technical profile of rest api, i used the client id and client secret values directly in default value of claim.
Is it possible to get the secret from key storage that is cryptographic keys and assign the claim?
Below is the technical profile of rest API,
<TechnicalProfile Id="TokenAPI">
<DisplayName>Rest API call</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/tenant.onmicrosoft.com/oauth2/v2.0/token</Item
>
<Item Key="AuthenticationType">None</Item>
<Item Key="SendClaimsIn">Form</Item>
<Item Key="HttpBinding">POST</Item>
<Item Key="AllowInsecureAuthInProduction">true</Item>
</Metadata>
<InputClaims>
<InputClaim
ClaimTypeReferenceId="client_id"
PartnerClaimType="client_id"
DefaultValue="abd2c507-xxxx-xxxx-xxxx-xxxx"
/>
<InputClaim
ClaimTypeReferenceId="client_secret"
PartnerClaimType="client_secret"
DefaultValue="LXz2L5xxxxxxxxxxxxxxxxxxxxxxxx"
/>
<InputClaim
ClaimTypeReferenceId="grant_type"
PartnerClaimType="grant_type"
DefaultValue="client_credentials"
/>
<InputClaim
ClaimTypeReferenceId="scope"
PartnerClaimType="scope"
DefaultValue="https://TitanB2CTest.onmicrosoft.com/507-xxxx-xxxx-xxxx-xxxx/.default"
/>
</InputClaims>
<OutputClaims>
<OutputClaim
ClaimTypeReferenceId="access_token"
PartnerClaimType="access_token"
/>
</OutputClaims>
<UseTechnicalProfileForSessionManagement ReferenceId="SM-Noop" />
</TechnicalProfile>
For the TokenAPI technical profile, you can set AuthenticationType to Basic and then add the <CryptographicKeys /> element, so that the client identifier and secret are sent in the Authorization: Basic xxx header to the token endpoint:
<TechnicalProfile Id="TokenAPI">
<DisplayName>Rest API call</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/tenant.onmicrosoft.com/oauth2/v2.0/token</Item>
<Item Key="AuthenticationType">Basic</Item>
<Item Key="SendClaimsIn">Form</Item>
</Metadata>
<CryptographicKeys>
<Key Id="BasicAuthenticationUsername" StorageReferenceId="B2C_1A_MyClientId" />
<Key Id="BasicAuthenticationPassword" StorageReferenceId="B2C_1A_MyClientSecret" />
</CryptographicKeys>
...
</TechnicalProfile>

Resources