Unknown Directive in Netlify Production using GRANDstack

Hi, has anyone encountered the following
errorMessage: "Unknown directive "isAuthenticated".↵↵Unknown directive "isAuthenticated"."

Everything works fine in development. It happens in production when serving through netlify functions.

const schema = makeAugmentedSchema({
  typeDefs,
  resolvers,
  config: {
    query: true,
    mutation: true,
    auth: {
      isAuthenticated: true,
      hasScope: true,
    },
  },
})

This is the code from my index.js file.

UPDATE:
Here is the code from my functions/graphql/graphql.js file

`// This module can be used to serve the GraphQL endpoint
// as a lambda function

const { ApolloServer } = require('apollo-server-lambda')
const { makeAugmentedSchema } = require('neo4j-graphql-js')
const neo4j = require('neo4j-driver')

// This module is copied during the build step
// Be sure to run `npm run build`
const { typeDefs } = require('./graphql-schema')

const driver = neo4j.driver(
  process.env.NEO4J_URI || 'bolt://localhost:7687',
  neo4j.auth.basic(
    process.env.NEO4J_USER || 'neo4j',
    process.env.NEO4J_PASSWORD || 'neo4j'
  ),
  {
    encrypted: process.env.NEO4J_ENCRYPTED ? 'ENCRYPTION_ON' : 'ENCRYPTION_OFF',
  }
)

const server = new ApolloServer({
  schema: makeAugmentedSchema({
    typeDefs,
    config: {
      query: true,
      mutation: true,
      auth: {
        isAuthenticated: true,
        hasScope: true,
      },
    },
  }),
  context: ({ req }) => {
    return { req, driver, neo4jDatabase: process.env.NEO4J_DATABASE }
  },
})

exports.handler = server.createHandler()
`

After including the config object in makeAugmentedSchema, the error has now changed to say no authorisation token

I'm not sure if there's anything else I should be doing. Everything works fine in development.

1 Like

I think we chatted about this on Discord, but posting here as well for others:

Netlify/lambda function invocations have a different signature (an event object is passed instead of req), but the graphql-auth-directive package will be looking for a req object to find the authorization header. So I don't think the headers are being captured in your example. Try returning the event object under the req key in the context function:

const server = new ApolloServer({
  schema,
  context: ({ event }) => {
    return {
      driver,
      req: event,
    };
  },
  introspection: true,
  playground: true,
});

Here's a longer example if you also want to decode a JWT and add claims from the JWT to cypherParams (NOTE: the new official @neo4j/graphql library does this automatically)

const server = new ApolloServer({
  schema,
  context: async ({ event }) => {
    const token = event.headers?.authorization?.slice(7);
    let userId;

    if (!token) {
      return {
        driver,
      };
    }

    const authResult = new Promise((resolve, reject) => {
      jwt.verify(
        token,
        getPublicKey,
        {
          algorithms: ["RS256"],
        },
        (error, decoded) => {
          if (error) {
            reject({ error });
          }
          if (decoded) {
            resolve(decoded);
          }
        }
      );
    });

    const decoded = await authResult;

    return {
      driver,
      req: event,
      cypherParams: {
        userId: decoded.sub,
      },
    };
  },
  introspection: true,
  playground: true,
});

Note that this is using the old neo4j-graphql-js, you may want to switch to the newer official @neo4j/graphql library which has a more powerful authorization model. The GRANDstack starter project has been updated to use this library instead of neo4j-graphql-js.

I was looking into making the switch to the new @neo4j/graphql library, but my code currently has a number of relationship properties in it. So I'm patiently waiting for support to come for relays and then I'll make the switch.

The first code snippet doesn't seem to work. I get the error You are not authorised for this resource

I tried the second snippet of code, but I'm not sure what the set up is for the variables jwt.verify as well as the getPublicKey.

Hi Will I implemented your second solution as the first one wasn't working, I'm not sure what the issue is, but I'm currently getting an error that says "Context creation failed: undefined"

Here is what my graphql.js file currently looks like.

// This module can be used to serve the GraphQL endpoint
// as a lambda function

const { ApolloServer } = require('apollo-server-lambda')
const { makeAugmentedSchema, assertSchema } = require('neo4j-graphql-js')
const neo4j = require('neo4j-driver')
const jwt = require('jsonwebtoken')

// This module is copied during the build step
// Be sure to run `npm run build`
const { typeDefs } = require('./graphql-schema')

const driver = neo4j.driver(
  process.env.NEO4J_URI || 'bolt://localhost:7687',
  neo4j.auth.basic(
    process.env.NEO4J_USER || 'neo4j',
    process.env.NEO4J_PASSWORD || 'neo4j'
  ),
  {
    encrypted: process.env.NEO4J_ENCRYPTED ? 'ENCRYPTION_ON' : 'ENCRYPTION_OFF',
  }
)

const schema = makeAugmentedSchema({
  typeDefs,
  config: {
    query: true,
    mutation: true,
    auth: {
      isAuthenticated: true,
      hasScope: true,
    },
  },
})

assertSchema({ schema, driver, debug: true })

const server = new ApolloServer({
  schema: schema,
  context: async ({ event }) => {
    const token = event.headers?.authorization?.slice(7)
    // let userId

    if (!token) {
      return {
        driver,
      }
    }

    const authResult = new Promise((resolve, reject) => {
      jwt.verify(
        token,
        process.env.JWT_SECRET,
        {
          algorithms: ['RS256'],
        },
        (error, decoded) => {
          if (error) {
            reject({ error })
          }
          if (decoded) {
            resolve(decoded)
          }
        }
      )
    })

    const decoded = await authResult

    return {
      driver,
      req: event,
      cypherParams: {
        userId: decoded.sub,
      },
      neo4jDatabase: process.env.NEO4J_DATABASE,
    }
  },
  introspection: true,
  playground: true,
})

exports.handler = server.createHandler()

At this point I'm wondering if sharing a link to my github repo would help any? I really want to get this app deployed as I'm using it for a project at work.

I am facing the same issue in my netlify deployment "You are not authorised for this resource". I have a similar config.auth object in the schema variable. Please let me know if you managed to fix this.

Hi
I've not been able to fix this, I'm actively looking for a solution to this as I'm using the @hasScope and @isAuthenticated directives in my project.

I shared my code with Will but this was his response on discord:

I don't see anything in functions/graphql/graphql.js that jumps out. Based on that error it sounds like something is causing an error in the context function. I wonder if maybe something with the JWT verification? I would suggest implementing the same thing in /api/src/index.js and testing locally which will make it easier to troubleshoot without the lambda deployment / overhead.

As far as I can see, it seems to be the JWT verification that is causing the issue. I'm still searching for a solution and would be glad to share if I find it. You can also share a solution if you find one.

I've been able to isolate the problem.

It seems that the event object is not getting returned in the context function.

const server = new ApolloServer({
  schema: schema,
  context: ({ event }) => {
    return {
      driver,
      req: event,
    }
  },
  introspection: true,
  playground: true,
})

I'm able to log the event object before the return statement but after that it just disappears after that. I'm guessing this is a problem with Apollo?