4
votes

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>
3
Hi, can you share any details on you were able to achieve the string formatted hasura claims with AAD B2C?Vishwasa Navada K
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.Dave

3 Answers

2
votes

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

0
votes

Can you share the desired/expected output of JSON that you want from B2C? If you review the doc: https://docs.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]"
  }
}

0
votes

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.