You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于Graphcool-Yoga与Prisma Bindings的Passport多策略认证配置问题

Alright, let's break down how to implement full authentication with Passport's Local, Bearer, GitHub, and Twitter strategies in your Graphcool-Yoga + Prisma Bindings project. I'll walk you through each step, including fixing your existing user resolver and completing the auth.js config.


1. First, Install Required Dependencies

Start by adding all the Passport strategies and supporting packages:

npm install passport passport-local passport-http-bearer passport-github2 passport-twitter bcryptjs express-session

2. Complete Passport Configuration (auth.js)

This file will handle all four authentication strategies, plus user serialization/deserialization for session-based OAuth flows:

const passport = require('passport');
const LocalStrategy = require('passport-local').Strategy;
const BearerStrategy = require('passport-http-bearer').Strategy;
const GitHubStrategy = require('passport-github2').Strategy;
const TwitterStrategy = require('passport-twitter').Strategy;
const bcrypt = require('bcryptjs');

// Initialize Passport
passport.initialize();

// --- Local Strategy (Username/Password Login) ---
passport.use(new LocalStrategy(
  async (username, password, done) => {
    try {
      // Fetch user via Prisma Bindings
      const user = await context.db.query.user({ where: { username } });
      if (!user) return done(null, false, { message: 'Invalid username' });

      // Validate password hash
      const passwordMatch = await bcrypt.compare(password, user.passwordHash);
      if (!passwordMatch) return done(null, false, { message: 'Invalid password' });

      // Return sanitized user data (exclude sensitive fields)
      return done(null, { id: user.id, username: user.username });
    } catch (err) {
      return done(err);
    }
  }
));

// --- Bearer Strategy (Token-Based Authentication) ---
passport.use(new BearerStrategy(
  async (token, done) => {
    try {
      // Fetch user with matching token (requires a `token` field in your Prisma User model)
      const user = await context.db.query.user({ where: { token } });
      if (!user) return done(null, false);

      return done(null, user, { scope: 'all' });
    } catch (err) {
      return done(err);
    }
  }
));

// --- GitHub OAuth Strategy ---
passport.use(new GitHubStrategy({
    clientID: process.env.GITHUB_CLIENT_ID,
    clientSecret: process.env.GITHUB_CLIENT_SECRET,
    callbackURL: "http://localhost:4000/auth/github/callback" // Update with your frontend/API URL
  },
  async (accessToken, refreshToken, profile, done) => {
    try {
      // Check if user already exists via GitHub ID
      let user = await context.db.query.user({ where: { githubId: profile.id } });
      
      // Create new user if they don't exist
      if (!user) {
        user = await context.db.mutation.createUser({
          data: {
            username: profile.username,
            githubId: profile.id,
            email: profile.emails?.[0]?.value || null,
            active: true
          }
        });
      }

      return done(null, user);
    } catch (err) {
      return done(err);
    }
  }
));

// --- Twitter OAuth Strategy ---
passport.use(new TwitterStrategy({
    consumerKey: process.env.TWITTER_CONSUMER_KEY,
    consumerSecret: process.env.TWITTER_CONSUMER_SECRET,
    callbackURL: "http://localhost:4000/auth/twitter/callback" // Update with your frontend/API URL
  },
  async (token, tokenSecret, profile, done) => {
    try {
      // Check if user already exists via Twitter ID
      let user = await context.db.query.user({ where: { twitterId: profile.id } });
      
      // Create new user if they don't exist
      if (!user) {
        user = await context.db.mutation.createUser({
          data: {
            username: profile.username,
            twitterId: profile.id,
            active: true
          }
        });
      }

      return done(null, user);
    } catch (err) {
      return done(err);
    }
  }
));

// Serialize/deserialize users for session-based OAuth flows
passport.serializeUser((user, done) => done(null, user.id));
passport.deserializeUser(async (id, done) => {
  try {
    const user = await context.db.query.user({ where: { id } });
    done(null, user);
  } catch (err) {
    done(err);
  }
});

module.exports = passport;

3. Integrate Passport with Graphcool-Yoga

Update your Yoga server setup to include Passport middleware and fix your user resolver:

const { GraphQLServer } = require('graphcool-yoga');
const passport = require('./auth');
const session = require('express-session');
const db = require('./prisma-bindings'); // Your Prisma Bindings instance

const resolvers = {
  Query: {
    user: async (root, args, context, info) => {
      // Fix: Use Passport's authenticate callback pattern instead of direct invocation
      return new Promise((resolve, reject) => {
        passport.authenticate('bearer', { session: false }, (err, user) => {
          if (err || !user) return reject(new Error('Not Authorised'));
          if (!args.id) return reject(new Error('Id cannot be empty'));

          // Fetch the requested user (add additional authorization checks if needed)
          context.db.query.user({ where: { id: args.id, active: true } }, info)
            .then(resolve)
            .catch(reject);
        })(context.request); // Pass Yoga's request object to Passport
      });
    }
  },
  Mutation: {
    // Local login mutation to return a bearer token
    localLogin: async (root, { username, password }, context) => {
      return new Promise((resolve, reject) => {
        passport.authenticate('local', { session: false }, (err, user) => {
          if (err || !user) return reject(new Error('Invalid credentials'));

          // Generate a secure token (use JWT in production instead of random bytes)
          const token = require('crypto').randomBytes(16).toString('hex');
          
          // Update user's token in the database
          context.db.mutation.updateUser({
            where: { id: user.id },
            data: { token }
          }).then(updatedUser => {
            resolve({ token, user: updatedUser });
          }).catch(reject);
        })({ body: { username, password } }); // Simulate Express request object
      });
    }
  }
};

// Initialize Yoga server
const server = new GraphQLServer({
  typeDefs: './schema.graphql',
  resolvers,
  context: req => ({ ...req, db })
});

// Add Express middleware for sessions (required for OAuth callbacks)
server.express.use(session({
  secret: 'your-strong-session-secret', // Replace with a secure secret in production
  resave: false,
  saveUninitialized: false
}));
server.express.use(passport.initialize());
server.express.use(passport.session()); // Enable session support for OAuth

// Add OAuth callback routes
server.express.get('/auth/github', passport.authenticate('github'));
server.express.get('/auth/github/callback', 
  passport.authenticate('github', { failureRedirect: '/' }),
  (req, res) => {
    // Generate token and redirect to frontend with token
    const token = require('crypto').randomBytes(16).toString('hex');
    req.db.mutation.updateUser({
      where: { id: req.user.id },
      data: { token }
    }).then(() => {
      res.redirect(`http://your-frontend-url?token=${token}`);
    });
  }
);

server.express.get('/auth/twitter', passport.authenticate('twitter'));
server.express.get('/auth/twitter/callback', 
  passport.authenticate('twitter', { failureRedirect: '/' }),
  (req, res) => {
    const token = require('crypto').randomBytes(16).toString('hex');
    req.db.mutation.updateUser({
      where: { id: req.user.id },
      data: { token }
    }).then(() => {
      res.redirect(`http://your-frontend-url?token=${token}`);
    });
  }
);

server.start(() => console.log('Server running on http://localhost:4000'));

4. Update Your Prisma Datamodel

Add these fields to your datamodel.prisma to support all authentication methods:

type User {
  id: ID! @unique
  username: String! @unique
  passwordHash: String # For local login (never store plaintext passwords!)
  token: String @unique # For bearer token authentication
  githubId: String @unique # For GitHub OAuth
  twitterId: String @unique # For Twitter OAuth
  email: String
  active: Boolean! @default(true)
  # Add your other user fields here
}

5. Key Production Notes

  • Bearer Token Security: Replace random token generation with JWT (use the jsonwebtoken package) to add expiration dates and signed claims.
  • Password Hashing: When registering users, always hash passwords with bcrypt.hash() before storing them in passwordHash.
  • Authorization Checks: Extend your resolvers to verify users have permission to access resources (e.g., a user should only be able to fetch their own data unless they're an admin).
  • Environment Variables: Store API keys, secrets, and callback URLs in environment variables (never hardcode them!).

内容的提问来源于stack exchange,提问作者Bharat Chhabra

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.26 09:05:31