Slash Lambda Field addGraphQLResolvers not passing JWT

Continuing the discussion from Slash Lambda Fileds addGraphQLResolver missing args:

Overview

In a lambda function, we should be able to run graphql requests either queries or mutations. What we want to do next is to fetch from an API and update one field when we request another field. What we found out while doing this is that while we can do graphql queries and mutations, the JWT token is not being passed along. This results in problems because now we either have to include a hard coded token or remove the auth update rule to allow this to work from a lambda.

There may be another bug here with passing variables to the graphql query/mutation even if the variables are hard coded. I will test again on that point with a simpler example to confirm.
UPDATE: No this was not another bug. We resolved this part I believe.

Schema:

type Contact @auth(
  update: { rule: "{$ROLE: { eq: \"ADMIN\" } }" }
) {
  id: ID
  name: String @search(by: [hash])
  fromAPI: String
  syncAPI: String @lambda
}

# Dgraph.Authorization {"VerificationKey":"My_S3cr3t!","Header":"token","Namespace":"https://my.app.com/claims","Algo":"HS256"}

JWT:

{
  "https://my.app.com/claims": {
    "ROLE": "ADMIN"
  },
  "iat": 1605923890,
  "exp": 1767225600,
  "iss": "myapp",
  "sub": "john.doe"
}

signed:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwczovL215LmFwcC5jb20vY2xhaW1zIjp7IlJPTEUiOiJBRE1JTiJ9LCJpYXQiOjE2MDU5MjM4OTAsImV4cCI6MTc2NzIyNTYwMCwiaXNzIjoibXlhcHAiLCJzdWIiOiJqb2huLmRvZSJ9.A2kpBagmpmI62hYTG-doCk3fxFhSF3PH_qdSo8jdIJI

Add Data

mutation {
  addContact(input: [{
    name: "Qux"
  }]) {
    numUids
    contact {
      id
      name
      fromAPI
    }
  }
}

response:

{
  "data": {
    "addContact": {
      "numUids": 1,
      "contact": [
        {
          "id": "0x7",
          "name": "Qux",
          "fromAPI": null
        }
      ]
    }
  },
  "extensions": {
    "touched_uids": 7,
    "tracing": {
      "version": 1,
      "startTime": "2021-01-14T19:50:52.329356205Z",
      "endTime": "2021-01-14T19:50:52.334463966Z",
      "duration": 5107780,
      "execution": {
        "resolvers": [
          {
            "path": [
              "addContact"
            ],
            "parentType": "Mutation",
            "fieldName": "addContact",
            "returnType": "AddContactPayload",
            "startOffset": 101131,
            "duration": 4979254,
            "dgraph": [
              {
                "label": "mutation",
                "startOffset": 148307,
                "duration": 2474818
              },
              {
                "label": "query",
                "startOffset": 3679377,
                "duration": 1373014
              }
            ]
          }
        ]
      }
    }
  }
}

Update From Slash API Explorer:

passing in the JWT above as the header token

mutation {
  updateContact(input: {
    filter: {
      name:{eq:"Qux"}
    }
    set: {
      fromAPI: "quux"
    }
  }) {
    numUids
    contact {
      id
      name
      fromAPI
    }
  }
}

response:

{
  "data": {
    "updateContact": {
      "numUids": 1,
      "contact": [
        {
          "id": "0x7",
          "name": "Qux",
          "fromAPI": "quux"
        }
      ]
    }
  },
  "extensions": {
    "touched_uids": 9,
    "tracing": {
      "version": 1,
      "startTime": "2021-01-14T20:00:17.206785379Z",
      "endTime": "2021-01-14T20:00:17.211507046Z",
      "duration": 4721686,
      "execution": {
        "resolvers": [
          {
            "path": [
              "updateContact"
            ],
            "parentType": "Mutation",
            "fieldName": "updateContact",
            "returnType": "UpdateContactPayload",
            "startOffset": 96834,
            "duration": 4599594,
            "dgraph": [
              {
                "label": "mutation",
                "startOffset": 217826,
                "duration": 1988950
              },
              {
                "label": "query",
                "startOffset": 3456080,
                "duration": 1206784
              }
            ]
          }
        ]
      }
    }
  }
}

Lambda:

async function syncAPI({graphql}) {
  const response = await graphql(`
    mutation {
      updateContact(input: {
        filter: {
          name:{eq:"Qux"}
        }
        set: {
          fromAPI: "quuz"
        }
      }) {
        numUids
        contact {
          id
          name
          fromAPI
        }
      }
    }
  `);
  return JSON.stringify(response)
}

self.addGraphQLResolvers({
  "Contact.syncAPI": syncAPI
})

Query from Slash API Explorer

query MyQuery {
  queryContact(filter: {name: {eq: "Qux"}}) {
    id
    name
    syncAPI
  }
}

response:

{
  "data": {
    "queryContact": [
      {
        "id": "0x7",
        "name": "Qux",
        "syncAPI": "{\"data\":{\"updateContact\":{\"numUids\":0,\"contact\":[]}},\"extensions\":{\"touched_uids\":2,\"tracing\":{\"version\":1,\"startTime\":\"2021-01-14T20:06:21.767509161Z\",\"endTime\":\"2021-01-14T20:06:21.771172522Z\",\"duration\":3663351,\"execution\":{\"resolvers\":[{\"path\":[\"updateContact\"],\"parentType\":\"Mutation\",\"fieldName\":\"updateContact\",\"returnType\":\"UpdateContactPayload\",\"startOffset\":96515,\"duration\":3551855,\"dgraph\":[{\"label\":\"mutation\",\"startOffset\":131853,\"duration\":1378019},{\"label\":\"query\",\"startOffset\":2541768,\"duration\":1090889}]}]}}}}"
      }
    ]
  },
  "extensions": {
    "touched_uids": 4,
    "tracing": {
      "version": 1,
      "startTime": "2021-01-14T20:06:21.742527393Z",
      "endTime": "2021-01-14T20:06:21.77845642Z",
      "duration": 35929029,
      "execution": {
        "resolvers": [
          {
            "path": [
              "queryContact"
            ],
            "parentType": "Query",
            "fieldName": "queryContact",
            "returnType": "[Contact]",
            "startOffset": 99917,
            "duration": 35803282,
            "dgraph": [
              {
                "label": "query",
                "startOffset": 234199,
                "duration": 1638815
              }
            ]
          }
        ]
      }
    }
  }
}

Notice numUids returned 0. No Contact was updated.

This would only be possible if the JWT was not passed through the lambda script.

Umm… @abhimanyusinghgaur, that is not true in my test case here.

Update: If we change this to be a Lambda Mutation instead of a Lambda Field, then the JWT seems to pass through. We also need the JWT on Lambda Fields though too. Why? In a Lambda Mutation we have to then pass an id of the node, where the Lambda Field already has that id in the parent. So this requires us to do another round trip to the database from the lambda to get the info from the Contact that we will need to send to the API we are working with, hence why we wanted to call it from a field.

@gja Do you have any input on this? Is the JEWT supposed to be passed through with Field resolvers?

@amaster507 @gja Anthony mentioned that Lambda Mutations do pass through the JWT. I haven’t found that to be the case. I can’t even find in the dgraph-lambda source code where it passes through the JWT.

Here’s my situation:

  • I’m able to run queries in API Explorer, Postman, HTTP Requests with @auth rules just fine by passing a JWT with the auth header I set in the schema.
  • When I try to run the same GraphQL query within a lambda (still passing in the exact same JWT) the return value from the query is exactly what I expect it to be if there is no JWT passed in.
  • I have verified that the query runs just fine inside the lambda if I remove the @auth rule and also runs just fine using DQL (which I don’t want to use because I want to use auth.

Do you have any insight into what might be happening, or could you even tell me where the dgraph-lambda library is passing through the JWT?

1 Like

Let me take another look here. From what I remember:

  1. the auth headers are passed via the json body
  2. the graphql function creates a closure that includes this

However, I’m also seeing this same behavior. Let me see if I can find the bug, and fix this today

3 Likes

Thank you so much!

Hi, this should work on the next update to your lambda script.

2 Likes

Do you mean that you implemented a fix of some sort and JWT tokens are passed? I am unclear on what ‘this’ is.

@pbassham Yes they did implement a fix. Check out the latest commit to the dgraph-lambda repo: Passing the authHeader correctly to the runner, · dgraph-io/dgraph-lambda@55ef609 · GitHub

Awesome!

So, this is live on Slash (or Cloud, as its called now), or is it just queued for the next Slash update?

@pbassham It’s live right now. I’ve tested it and it’s working! Thanks @gja!

3 Likes

This went live earlier today. Please update your lambda script for the change to take effect

1 Like