1
votes

I am trying to authenticate a user before allowing access to my '/graphql' endpoint.

According to apollo-server documentation regarding setting a context I can do something like this.

app.use(
  '/graphql',
  bodyParser.json(),
  graphqlExpress(req => {
    // Some sort of auth function
    const userForThisRequest = getUserFromRequest(req);

    return {
      schema: myGraphQLSchema,
      context: {
        user: userForThisRequest,
      },
      // other options here
    };
  }),
);

I am trying to use passportJS's authenticate() function in the placeholder for "Some sort of auth function", but I can't seem to understand how to utilize the 'req' parameter that I have access to. Should I call passport.authenticate() after the bodyParser middleware or inside the graphqlExpress method?

So my question is how can I use passportJS's authenticate mechanism in this context? Also, is this the best way to implement Authentication on Apollo-server?

1

1 Answers

3
votes

There's a couple of different ways you could do this -- depending on the type of response you want to send back to your client when authentication fails and the how much you need to fine-tune the authentication process.

Passport's authenticate function is effectively just express middleware, so you can do something like:

app.use(
  '/graphql',
  bodyParser.json(),
  authenticate(),
  graphqlExpress(req => ({
    schema: myGraphQLSchema,
    context: {
      user: getUserFromRequest(req),
    },
  }));
);

authenticate will send a response with a 401 status if authentication fails (the response itself depends on how you configured the verify callback in your passport strategy). That means if authentication fails, the Apollo server middleware will never be called.

Alternatively, you could avoid using authenticate and handle checking the authentication yourself. This can be done at the resolver level, or for all resolvers by utilizing graphql-tool's addSchemaLevelResolveFunction. import { addSchemaLevelResolveFunction } from 'graphql-tools'

addSchemaLevelResolveFunction(executableSchema, (root, args, ctx, info) => {
  if (!ctx.user) throw new CustomAuthenticationError()
})

The biggest difference is that your response will now return a 200 status, and will include a null data property and an errors array that includes the authentication error.

Of course, the second approach also lets you fine tune your authentication logic -- if you want to only limit a subset of queries or mutations to be only available to authenticated users, for example. Barring that, I don't know if either approach is necessarily better.