0

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:

  1. Is there any work around to get login-NonInteractive to POST as it should (without requiring a self-asserted technical profile)?
  2. 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>
    ```
Dessus
  • 2,147
  • 1
  • 14
  • 24
  • I noticed this also restricts url query string inputs into self-asserted profiles: https://stackoverflow.com/questions/53008134/oauth-kv-claims-resolver-in-aad-b2c-does-not-work (see bottom comment). Gut feel is, I need to setup my own service endpoint to do this and issue the token using the graph API somehow – Dessus Dec 11 '22 at 04:56
  • 1
    You should be using [client_credentials](https://learn.microsoft.com/en-us/azure/active-directory-b2c/client-credentials-grant-flow?pivots=b2c-custom-policy) for M2M. – Jas Suri - MSFT Dec 12 '22 at 12:01
  • Thanks @JasSuri-MSFT. I was having issue when using that before as it has: but no reference to that. I assume the default journey is overriden somehow and that journey is arbitrary in that example? Somehow giving the client credentials is executing the 'JWTIssuer' which triggers the subflow. I was executing the flow from the portal; but now that I think about it I would need to execute it with a JWT issued from graph API for the client creds. I think you have put me on the right track. Will update with answer if I work it out. – Dessus Dec 13 '22 at 04:16
  • In step 4 there is a link with full XML sample. You don’t run this from the portal, test it with POSTman, the article explains how to run it. – Jas Suri - MSFT Dec 13 '22 at 08:51
  • Its unclear with step 4 how to actually call the policy url. I have my JWT at the client credential level, but how do I pass the JWT into the custom policy? Is it a particular query param on the url (I assume its not just an authorization header (ie bearer ey....) in the http request. From reading this: https://medium.com/the-new-control-plane/invoking-the-azure-ad-b2c-custom-policy-journey-with-a-jwt-e9e3ff0560a3 it looks like it might be: &client_assertion=&client_assertion_type=?> I am keen for options for user level and client level JWTs. – Dessus Dec 13 '22 at 09:50
  • My last comment should be ignored in favour of what I have written up as an answer – Dessus Dec 14 '22 at 22:00

1 Answers1

0

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.

Dessus
  • 2,147
  • 1
  • 14
  • 24
  • Have noticed that these don't work on the replying party and that causes invalid_grant issues: ``` ``` Where as the same thing with a hard coded default value works – Dessus Dec 15 '22 at 02:28
  • I got around the variables causing grant issues in the replying party by moving the assignment into a claims technical profile. @Jas-Suri-MSFT I am however at a loss of how to get query params in my custom policy from the url in the curl above. I am trying to add ?myinfo=abc to the end of it but can't get it through using: {OAUTH-KV:myinfo}. Is there a way to do this (either via the body or via query params or via a predefined query param?) – Dessus Dec 15 '22 at 03:08
  • 1
    Show the technical profile where you are resolving with `{OAUTH-KV:myinfo}`. Maybe open a new post. – Jas Suri - MSFT Dec 15 '22 at 09:43
  • Have put it as a new question as you suggest here: https://stackoverflow.com/questions/74845000/azureb2c-client-credentials-and-query-parameters-in-a-custom-policy . Thanks Jas! – Dessus Dec 18 '22 at 23:14