AAD B2C: Output a nested JSON object in a JWT for Hasura
Asked Answered
S

3

6

We are using Hasura to provide our GraphQL API to consumers. Currently we use Auth0 to authenticate users, but we would like to migrate to Azure AD B2C.

A requirement of JWT security with Hasura is using the "https://hasura.io/jwt/claims" namespace to provide custom claims (such as X-Hasura-Org-Id, X-Hasura-App-Id, etc).

I have been able to get AAD B2C to:

  1. Gather the required values for these custom claims using a REST API;
  2. Transform the individual string / stringCollection values into a JSON object using a ClaimsTransformation; and
  3. Return the transformed claims in the JWT.

However, I cannot figure out how to get the JSON object to appear in the final JWT without the contents being escaped - i.e. being output as a string rather than an object.

Is AAD B2C capable of outputting nested objects in a JWT?


What we're hoping to achieve

This is what Hasura wants the JWT namespace to look like (note the https://hasura.io/jwt/claims object)

{
  "exp": 1588405829,
  "nbf": 1588402229,
  "ver": "1.0",
  "iss": "https://<redacted>.b2clogin.com/<redacted>/v2.0/",
  "sub": "<redacted>",
  "aud": "<redacted>",
  "acr": "b2c_1a_aaa_signupsignin",
  "nonce": "defaultNonce",
  "iat": 1588402229,
  "auth_time": 1588402229,
  "given_name": "Test",
  "family_name": "User",
  "name": "Test User",
  "email": "[email protected]",
  "idp": "facebook.com",
  "https://hasura.io/jwt/claims": {
    "x-hasura-allowed-roles":["role1","role2","role3"],
     "x-hasura-default-role":"role1",
     "x-hasura-org-id":"test-org",
     "x-hasura-user-id":"test-user-id",
     "x-hasura-app-id":"<redacted>"
  }
}

What we're getting at the moment

Here's an example of the JWT from AAD B2C:

{
  "exp": 1588405829,
  "nbf": 1588402229,
  "ver": "1.0",
  "iss": "https://<redacted>.b2clogin.com/<redacted>/v2.0/",
  "sub": "<redacted>",
  "aud": "<redacted>",
  "acr": "b2c_1a_aaa_signupsignin",
  "nonce": "defaultNonce",
  "iat": 1588402229,
  "auth_time": 1588402229,
  "given_name": "Test",
  "family_name": "User",
  "name": "Test User",
  "email": "[email protected]",
  "idp": "facebook.com",
  "https://hasura.io/jwt/claims": "{\"x-hasura-allowed-roles\":[\"role1\",\"role2\",\"role3\"],\"x-hasura-default-role\":\"role1\",\"x-hasura-org-id\":\"test-org\",\"x-hasura-user-id\":\"test-user-id\",\"x-hasura-app-id\":\"<redacted>\"}"
}

There doesn't appear to be an option to store a claim as an object, only a string.


How we got there

An example of the ClaimsTransformation:

<ClaimsTransformation Id="hasuraClaimsToJson" TransformationMethod="GenerateJson">
  <InputClaims>
    <InputClaim ClaimTypeReferenceId="x-hasura-allowed-roles" TransformationClaimType="x-hasura-allowed-roles" />
    <InputClaim ClaimTypeReferenceId="x-hasura-default-role" TransformationClaimType="x-hasura-default-role" />
    <InputClaim ClaimTypeReferenceId="x-hasura-org-id" TransformationClaimType="x-hasura-org-id" />
    <InputClaim ClaimTypeReferenceId="x-hasura-user-id" TransformationClaimType="x-hasura-user-id" />
  </InputClaims>
  <InputParameters>
    <InputParameter Id="x-hasura-app-id" DataType="string" Value="internal-redacted-uuid" />
  </InputParameters>
  <OutputClaims>
    <OutputClaim ClaimTypeReferenceId="hasuraClaims" TransformationClaimType="outputClaim" />
  </OutputClaims>
</ClaimsTransformation>

Example RelyingParty config:

<RelyingParty>
  <DefaultUserJourney ReferenceId="SignUpOrSignIn" />
  <TechnicalProfile Id="PolicyProfile">
    <DisplayName>PolicyProfile</DisplayName>
    <Protocol Name="OpenIdConnect" />
    <OutputClaims>
      <OutputClaim ClaimTypeReferenceId="displayName" />
      <OutputClaim ClaimTypeReferenceId="givenName" />
      <OutputClaim ClaimTypeReferenceId="surname" />
      <OutputClaim ClaimTypeReferenceId="email" />
      <OutputClaim ClaimTypeReferenceId="hasuraClaims" PartnerClaimType="https://hasura.io/jwt/claims" />
      <OutputClaim ClaimTypeReferenceId="objectId" PartnerClaimType="sub"/>
      <OutputClaim ClaimTypeReferenceId="identityProvider" />
    </OutputClaims>
    <SubjectNamingInfo ClaimType="sub" />
  </TechnicalProfile>
</RelyingParty>
Saratov answered 2/5, 2020 at 7:18 Comment(2)
Hi, can you share any details on you were able to achieve the string formatted hasura claims with AAD B2C?Uncouth
Sure! We used a RESTful API to retrieve the claims values and populated them using the ClaimsTransformation and RelyingParty configs above. We followed Microsoft's Integrate REST API claims exchanges in your Azure AD B2C custom policy document. I'll do a blog post with examples and share here.Saratov
U
3

Have you considered passing claims_format in HASURA_GRAPHQL_JWT_SECRET with stringified_json, so that Hasura can accept claims as string instead of object. I found the documentation entry on it here: https://hasura.io/docs/1.0/graphql/manual/auth/authentication/jwt.html

enter image description here

Uncouth answered 9/6, 2020 at 12:0 Comment(1)
Thanks @vishwas-navada-k, I hadn't seen that feature in the documentation previously! That indeed would solve this particular problem. We ended up shifting to webhook auth so we could take advantage of some more advanced authentication options, but your solution would be the best one if there's somebody else out there that runs in to the same problem.Saratov
M
0

Can you share the desired/expected output of JSON that you want from B2C? If you review the doc: https://learn.microsoft.com/en-us/azure/active-directory-b2c/json-transformations, B2C is capable of returning a complex JSON.

Sample JSON Output by GenerateJSON:

  "personalizations": [
    {
      "to": [
        {
          "email": "[email protected]"
        }
      ],
      "dynamic_template_data": {
        "otp": "346349",
        "verify-email" : "[email protected]"
      },
      "subject": "Contoso account email verification code"
    }
  ],
  "template_id": "d-989077fbba9746e89f3f6411f596fb96",
  "from": {
    "email": "[email protected]"
  }
}

Mullein answered 4/5, 2020 at 18:53 Comment(1)
Thanks Razi. I followed that documentation and am successfully getting the JSON transformation to work, but the result is being returned as a string in the JWT rather than a subordinate object. Have added an example of the JWT we need.Saratov
S
0

For those interested, it turns out that this is not currently possible with Azure AD B2C.

Instead, we switched from using internal JWT auth in Hasura to using webhooks, and validated the B2C token using a small Node.js Function App.


EDIT:

As per the answer above, Hasura has a workaround using the claims_format parameter. This means that you can potentially use AAD B2C for Hasura authentication without the need for implementing Webhooks.

Saratov answered 28/5, 2020 at 6:46 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.