Kevin
Kevin

Reputation: 497

How to use passport-local with graphql

I'm trying to implement GraphQL in my project and I would like to use passport.authenticate('local') in my login Mutation

Code adaptation of what I want:

const typeDefs = gql`
type Mutation {
      login(userInfo: UserInfo!): User
    }
`

 const resolvers = {
    Mutation: {
      login: (parent, args) => { 
        passport.authenticate('local')
        return req.user
    }
}

Questions:

  1. Was passport designed mostly for REST/Express?
  2. Can I manipulate passport.authenticate method (pass username and password to it)?
  3. Is this even a common practice or I should stick to some JWT library?

Upvotes: 10

Views: 7096

Answers (3)

thisismydesign
thisismydesign

Reputation: 25072

You should definitely use passport unless your goal is to learn about authentication in depth.

I found the most straightforward way to integrate passport with GraphQL is to:

  • use a JWT strategy
  • keep REST endpoints to authenticate and retrieve tokens
  • send the token to the GraphQL endpoint and validate it on the backend

Why?

  • If you're using a client-side app, token-based auth is the best practice anyways.
  • Implementing REST JWT with passport is straightforward. You could try to build this in GraphQL as described by @jkettmann but it's way more complicated and less supported. I don't see the overwhelming benefit to do so.
  • Implementing JWT in GraphQL is straightforward. See e.g. for express or NestJS

To your questions:

Was passport designed mostly for REST/Express?

Not in principle, but you will find most resources about REST and express.

Is this even a common practice or I should stick to some JWT library?

Common practice is to stick to JWT.


More details here: OAuth2 in NestJS for Social Login (Google, Facebook, Twitter, etc)

Example project bhere: https://github.com/thisismydesign/nestjs-starter

Upvotes: 1

jkettmann
jkettmann

Reputation: 707

It took me a while to wrap my head around the combination of GraphQL and Passport. Especially when you want to use the local strategy together with a login mutation makes life complicated. That's why I created a small npm package called graphql-passport.

This is how the setup of the server looks like.

import express from 'express';
import session from 'express-session';
import { ApolloServer } from 'apollo-server-express';
import passport from 'passport';
import { GraphQLLocalStrategy, buildContext } from 'graphql-passport';

passport.use(
  new GraphQLLocalStrategy((email, password, done) => {
    // Adjust this callback to your needs
    const users = User.getUsers();
    const matchingUser = users.find(user => email === user.email && password === user.password);
    const error = matchingUser ? null : new Error('no matching user');
    done(error, matchingUser);
  }),
);

const app = express();
app.use(session(options)); // optional
app.use(passport.initialize());
app.use(passport.session()); // if session is used

const server = new ApolloServer({
  typeDefs,
  resolvers,
  context: ({ req, res }) => buildContext({ req, res, User }),
});

server.applyMiddleware({ app, cors: false });

app.listen({ port: PORT }, () => {
  console.log(`🚀 Server ready at http://localhost:${PORT}${server.graphqlPath}`);
});

Now you will have access to passport specific functions and user via the GraphQL context. This is how you can write your resolvers:

const resolvers = {
  Query: {
    currentUser: (parent, args, context) => context.getUser(),
  },
  Mutation: {
    login: async (parent, { email, password }, context) => {
      // instead of email you can pass username as well
      const { user } = await context.authenticate('graphql-local', { email, password });

      // only required if express-session is used
      context.login(user);

      return { user }
    },
  },
};

The combination of GraphQL and Passport.js makes sense. Especially if you want to add more authentication providers like Facebook, Google and so on. You can find more detailed information in this blog post if needed.

Upvotes: 10

Daniel Rearden
Daniel Rearden

Reputation: 84687

Passport.js is a "Express-compatible authentication middleware". authenticate returns an Express middleware function -- it's meant to prevent unauthorized access to particular Express routes. It's not really suitable for use inside a resolver. If you pass your req object to your resolver through the context, you can call req.login to manually login a user, but you have to verify the credentials and create the user object yourself before passing it to the function. Similarly, you can call req.logout to manually log out a user. See here for the docs.

If you want to use Passport.js, the best thing to do is to create an Express app with an authorization route and a callback route for each identify provider you're using (see this for an example). Then integrate the Express app with your GraphQL service using apollo-server-express. Your client app will use the authorization route to initialize the authentication flow and the callback endpoint will redirect back to your client app. You can then add req.user to your context and check for it inside resolvers, directives, GraphQL middleware, etc.

However, if you are only using local strategy, you might consider dropping Passport altogether and just handling things yourself.

Upvotes: 16

Related Questions