Apollo服务器-仅通过Passport-JWT将身份验证应用于某些解析器

时间:2019-02-01 02:18:13

标签: graphql passport.js apollo apollo-server passport-jwt

我目前有一个运行Express和Passport.js的Node.js后端以进行身份​​验证,并尝试使用Apollo Server切换到GraphQL。我的目标是实现与当前使用的身份验证相同的身份验证,但是无法弄清楚如何在允许其他解析器启用授权的同时将某些解析器公开。 (我已经尝试过广泛研究这个问题,但到目前为止还没有找到合适的解决方案。)

这是我目前的代码:

我的JWT策略:

const opts = {};
opts.jwtFromRequest = ExtractJwt.fromAuthHeaderAsBearerToken();
opts.secretOrKey = JWT_SECRET;

module.exports = passport => {
  passport.use(
    new JwtStrategy(opts, async (payload, done) => {
      try {
        const user = await UserModel.findById(payload.sub);
        if (!user) {
          return done(null, false, { message: "User does not exist!" });
        }
        done(null, user);
      } catch (error) {
        done(err, false);
      }
    })
  );
}

我的server.js和Apollo配置: (我目前正在从HTTP标头中提取承载令牌,并使用上下文对象将其传递给我的解析器):

const apollo = new ApolloServer({
  typeDefs,
  resolvers,
  context: async ({ req }) => {
    let authToken = "";

    try {
      if (req.headers.authorization) {
        authToken = req.headers.authorization.split(" ")[1];
      }
    } catch (e) {
      console.error("Could not fetch user info", e);
    }

    return {
      authToken
    };
  }
});

apollo.applyMiddleware({ app });

最后,我的解析器:

exports.resolvers = {
  Query: {
    hello() {
      return "Hello world!";
    },
    async getUserInfo(root, args, context) {
      try {
        const { id } = args;
        let user = await UserModel.findById(id);
        return user;
      } catch (error) {
        return "null";
      }
    },
    async events() {
      try {
        const eventsList = await EventModel.find({});
        return eventsList;
      } catch (e) {
        return [];
      }
    }
  }
};

我的目标是使某些查询(例如第一个查询(“ hello”))公开,同时将其他查询限制为仅使用有效承载令牌的请求。 不确定如何使用Passport.js和Passport-JWT在解析器中具体实现此授权(通常是通过将中间件添加到某些端点来完成,但是由于我只有一个端点(/ graphql)在此示例中,该选项会将所有查询仅限制为经过身份验证的用户,这不是我想要的。我必须以某种方式在解析器中执行授权,但不确定如何使用Passport.js中提供的工具来执行此操作)

任何建议都将不胜感激!

1 个答案:

答案 0 :(得分:1)

我将创建一个架构指令以对字段定义进行授权查询,然后在我想应用授权的任何地方使用该指令。示例代码:

class authDirective extends SchemaDirectiveVisitor {
    visitObject(type) {
        this.ensureFieldsWrapped(type);
        type._requiredAuthRole = this.args.requires;
    }

    visitFieldDefinition(field, details) {
        this.ensureFieldsWrapped(details.objectType);
        field._requiredAuthRole = this.args.requires;
    }

    ensureFieldsWrapped(objectType) {
        // Mark the GraphQLObjectType object to avoid re-wrapping:
        if (objectType._authFieldsWrapped) return;
        objectType._authFieldsWrapped = true;

        const fields = objectType.getFields();

        Object.keys(fields).forEach(fieldName => {
            const field = fields[fieldName];
            const {
                resolve = defaultFieldResolver
            } = field;
            field.resolve = async function (...args) {
                // your authorization code 
                return resolve.apply(this, args);
            };
        });
    }

}

并在类型定义中声明

directive @authorization(requires: String) on OBJECT | FIELD_DEFINITION

您的架构中的地图架构指令

....
resolvers,
schemaDirectives: {
authorization: authDirective
}

然后在您的api端点或任何对象上使用它

Query: {
    hello { ... }
    getuserInfo():Result @authorization(requires:authToken) {...} 
    events():EventResult @authorization(requires:authToken) {...} 
  };