graphql + apollo-server-express,如何处理 express authMiddleware 中的身份验证错误?

Posted

技术标签:

【中文标题】graphql + apollo-server-express,如何处理 express authMiddleware 中的身份验证错误?【英文标题】:graphql + apollo-server-express, how to handle auth error in express authMiddleware? 【发布时间】:2019-01-09 16:58:12 【问题描述】:

我无法弄清楚如何在我的 authMiddleware 函数中处理身份验证错误。

这是我的authMiddleware 函数,带有传统的快速错误处理方式。

const jwt = require('jsonwebtoken');
const  appConfig  = require('../config');

function authMiddleware(req, res, next) 
  let token;
  const parts = req.headers.authorization.split(' ');

  if (parts.length === 2) 
    const schema = parts[0];
    const credentials = parts[1];

    if (/^Bearer$/i.test(schema)) 
      token = credentials;
     else 
      // throw new Error();
      next(new Error('credentials_bad_scheme: Format is Authorization: Bearer [token]'));
    
  

  try 
    const  user  = jwt.verify(token, appConfig.JWT_SCERET);
    req.user = user;
   catch (error) 
    // console.log(error);
    next(error);
  
  next();


exports.authMiddleware = authMiddleware;

但是使用apollo-server-expressgraphql 系统。传递给next 函数的error 不能正常工作。这意味着当使用graphql 工具堆栈时,express 错误处理方式似乎不再起作用。

authMiddleware 中的 error 不会通过下面的 express 错误处理中间件

app.use((err, req, res) => 
  console.log('error handler: ', err);
);

如果我在身份验证失败时在catch 中使用return res.status(401).json(code: 1001, msg: 'Authorization failed')throw new Error('xxx')。该请求将永远停止在这里,这意味着永远不会下降到graphqlExpressHandler。为了让请求下降到graphqlExpressHandler,我只能对错误做的就是使用console.log 打印它们。

并且没有办法使用express-jwtunless方法或credentialsRequired属性。 因为在使用 graphql 时,只有一个名为 '/graphql' 的路由。所以,你不能,除非 /graphql route

解决这个问题的一种方法是:为auth 制作restful api 并以传统方式处理它。制作graphql api进行数据查询。

【问题讨论】:

你想通了吗?我也面临这个问题。 您可以将问题中提出的身份验证逻辑抽象为一个单独的函数,该函数只需要一个标题。然后在您的“正常”快速路由中,您使用中间件来运行身份验证代码,但在 Apollo 中,您使用上下文创建来调用您的函数。 遇到了同样的问题,使用 Express 中间件进行身份验证,API 的其余部分在 Apollo 服务器中运行。因此,常规的 Apollo 错误处理不会捕获服务器外部的错误。 :// 【参考方案1】:

迟到的答案,但可能对面临同样问题的人有所帮助。

我们是这样解决的:

graphql + apollo-server-express 仅公开 /graphql 路由,因此简单而好的方法是将身份验证端点公开为 graphql 突变,并在 @987654324 中进行令牌验证(您的 authMiddleware 所做的) @函数传递给ApolloServer实例。

示例:

    定义token突变。
// graphql.ts
import  gql  from 'apollo-server-express';
import AuthnHandler from './handlers/authn_handler';

export const typeDefs = gql`
  type Mutation 
    token(username: String, password: String): String
  
`

const authnHandler = new AuthnHandler();

export const resolvers = 
  Mutation: 
    token: authnHandler.tokenResolver
  
;
    定义验证凭据和颁发令牌的令牌变异解析器。
// handlers/authn_handler.ts
import  AuthenticationError  from 'apollo-server-express';

export default class AuthnHandler 
  public async tokenResolver(parent: any, args: any, context: any, info: any): Promise<any> 
    const username = args.username;
    const password = args.password;
    // pseudo-code here, replace with your token issuing implementation.
    // if credentials are valid, return Promise.resolve(token);
    // else throw new AuthenticationError('Invalid credentials.');
  

    定义验证授权标头中令牌的上下文函数(您的 authMiddleware 函数所做的事情)。
// server.ts
import express from 'express';
import  ApolloServer, ApolloServerExpressConfig  from 'apollo-server-express';
import  typeDefs, resolvers  from './graphql';
import  authMiddleware  from './auth_middleware';

const expressApp = express();

const apolloServer = new ApolloServer(
  typeDefs,
  resolvers,
  context: authMiddleware
 as ApolloServerExpressConfig);

apolloServer.applyMiddleware( app: expressApp );

expressApp.listen(3000, () => 
  console.log('server listening on port 3000');
);

您的authMiddleware 函数签名会根据context 函数要求更改,在这种情况下,它会在成功时返回请求对象本身。

// auth_middleware.ts
const jwt = require('jsonwebtoken');
const  appConfig  = require('../config');

function authMiddleware( req ) 
  let token;
  const parts = req.headers.authorization.split(' ');

  if (parts.length === 2) 
    const schema = parts[0];
    const credentials = parts[1];

    if (/^Bearer$/i.test(schema)) 
      token = credentials;
     else 
      throw new Error();
    
  

  try 
    const  user  = jwt.verify(token, appConfig.JWT_SCERET);
    req.user = user;
   catch (error) 
    throw new Error();
  
  return  req ;


exports.authMiddleware = authMiddleware;

apollo-server 文档中的authentication section 提供了这种实现方式的详细说明。

【讨论】:

以上是关于graphql + apollo-server-express,如何处理 express authMiddleware 中的身份验证错误?的主要内容,如果未能解决你的问题,请参考以下文章

GraphQl 和 insomnia 桌面客户端无法使用 graphql.org/swapi-graphql

使用 Express-GraphQL 的 GraphQL 订阅

数组中类型为 graphql 对象的 GraphQL 字段

Graphql 突变查询不适用于 express-graphql

[GraphQL] Query GraphQL Interface Types in GraphQL Playground

GraphQL - 无法缓存数据是 GraphQL 的一大弱点?