Authentication Graphql@include with expression

Authentication Graphql@include with expression,authentication,permissions,authorization,graphql,Authentication,Permissions,Authorization,Graphql,我正在实现一个查询,它应该根据用户登录状态在响应中提供一些字段 具体来说,我只想在传递了$authenticationToken的情况下获取“pointRate”字段,并且希望避免在下面的查询中传递$authenticated。我希望避免发送$authenticated的原因是客户端可能会错误地发送$authenticated=true但$authenticationToken=null query ToDoQuery($authenticationToken: String, $authent

我正在实现一个查询,它应该根据用户登录状态在响应中提供一些字段

具体来说,我只想在传递了
$authenticationToken
的情况下获取“pointRate”字段,并且希望避免在下面的查询中传递
$authenticated
。我希望避免发送
$authenticated
的原因是客户端可能会错误地发送
$authenticated=true
$authenticationToken=null

query ToDoQuery($authenticationToken: String, $authenticated: Boolean!) {
    pointRate(accessToken: $authenticationToken) @include(if: $authenticated) {
        status
    }
}

我认为这是不可能的,因为在GraphQL中不能将(空)字符串转换为布尔值

此外,来自以下方面的一些建议:

将授权逻辑委托给业务逻辑层


那么,实际上你想这么做

i) 如果传递了$authenticationToken,则需要获取“pointRate”

ii)并且您还希望避免在后续操作中传递$authenticated 查询。因为你关心的是你的客户,他们可以做出一些 错误,如发送认证为真,其中认证令牌 是空的

一般来说,我想回答,如果您想自己使用GraphQL处理身份验证,首先必须创建一个令牌,然后必须在每个请求或后续请求中传递该令牌。否则这是不可能的。因为在没有身份验证的情况下不会提供敏感数据

另一方面,您可以使用会话身份验证。在会话关闭之前,您可以访问所有数据

如果不满意,您可以使用类似您的场景阅读以下简要说明。我还试图积累一些相关的示例解决方案,以便更好地理解,这可能会使您更清楚

由于GraphQLAPI是完全公开的,您可以通过两种方式进行身份验证

  • 让web服务器(如express或nginx)进行身份验证。
  • 在GraphQL本身中处理身份验证。
  • 如果您在web服务器中执行身份验证,则可以使用标准的身份验证包(例如,passport.js for express),许多现有的身份验证方法都是现成的。您还可以随意添加和删除方法,而无需修改GraphQL模式

    如果您自己正在实施身份验证,请执行以下操作

    import jwt from'express-jwt';
    import graphqlHTTP from'express-graphql';
    import express from'express';
    import schema from'./mySchema';
    const app = express();
    
    app.use('/graphql', jwt({
      secret: 'shhhhhhared-secret',
      requestProperty: 'auth',
      credentialsRequired: false,
    }));
    app.use('/graphql', function(req, res, done) {
      const user = db.User.get(req.auth.sub);
      req.context = {
        user: user,
      }
      done();
    });
    app.use('/graphql', graphqlHTTP(req => ({
        schema: schema,
        context: req.context,
      })
    ));
    
    • 确保从不以明文或MD5或SHA-256格式存储密码 散列

      • 使用类似于bcrypt的东西

      • 请确保不要像在服务器上那样存储会话令牌,否则会丢失会话令牌 你应该先把它们切碎

      • 您可以编写一个登录方法来设置上下文。自突变 一个接一个地执行,而不是并行执行,您可以确定 在登录后设置上下文:

        突变{
        使用令牌登录(令牌:“6e37a03e-9ee4-42fd-912d-3f67d2d0d852”),
        做些事情(问候语:“你好”,名字:“汤姆”),
        做更多的东西(潜艇颜色:“黄色”)
        }

      • 我们将其作为GraphQL查询的一部分,而不是通过头或查询参数(如JWT、OAuth等)传递令牌。模式代码可以使用JWT库本身或其他工具直接解析令牌

      • 请记住在传递敏感信息时始终使用HTTPS:)
    因为并行执行对于性能来说是非常重要的。变异和查询按照给定的顺序连续执行。 因此,在大多数情况下,最好在web服务器中处理身份验证。它不仅更通用,而且更灵活


    场景: 首先检查以下内容

    import jwt from'express-jwt';
    import graphqlHTTP from'express-graphql';
    import express from'express';
    import schema from'./mySchema';
    const app = express();
    
    app.use('/graphql', jwt({
      secret: 'shhhhhhared-secret',
      requestProperty: 'auth',
      credentialsRequired: false,
    }));
    app.use('/graphql', function(req, res, done) {
      const user = db.User.get(req.auth.sub);
      req.context = {
        user: user,
      }
      done();
    });
    app.use('/graphql', graphqlHTTP(req => ({
        schema: schema,
        context: req.context,
      })
    ));
    
    如果您检查上述部分,您将发现API根本不安全。它可能会尝试验证JWT,但如果JWT不存在或无效,请求仍将通过(请参阅credentialsRequired:false)。为什么?我们必须允许请求通过,因为如果我们阻止它,我们将阻止整个API。这意味着,我们的用户甚至不能调用loginUser来获取令牌来进行身份验证


    解决方案#1: 使用身份验证解析程序而不是端点的裸体示例。

    import { GraphQLSchema } from 'graphql';
    import { Registry } from 'graphql-helpers';
    
    // The registry wraps graphql-js and is more concise
    const registry = new Registry();
    
    registry.createType(`
      type User {
        id: ID!
        username: String!
      }
    `;
    
    registry.createType(`
      type Query {
        me: User
      }
    `, {
      me: (parent, args, context, info) => {
        if (context.user) {
          return context.user;
        }
        throw new Error('User is not logged in (or authenticated).');
      },
    };
    
    const schema = new GraphQLSchema({
      query: registry.getType('Query'),
    });
    
    当请求到达Query.me解析器时,服务器中间件已经尝试对用户进行身份验证并从数据库中获取用户对象。在我们的解析器中,我们可以检查用户的graphql上下文(我们在server.js文件中设置了上下文),如果存在,则返回它,否则抛出错误

    注意:您可以很容易地返回null而不是抛出错误,我建议您这样做

    解决方案#2: 使用express graphql的功能组合(基于中间件)

    import { GraphQLSchema } from 'graphql';
    import { Registry } from 'graphql-helpers';
    // See an implementation of compose https://gist.github.com/mlp5ab/f5cdee0fe7d5ed4e6a2be348b81eac12
    import { compose } from './compose';
    
    const registry = new Registry();
    
    /**
    * The authenticated function checks for a user and calls the next function in the composition if
    * one exists. If no user exists in the context then an error is thrown.
    */
    const authenticated =
      (fn: GraphQLFieldResolver) =>
      (parent, args, context, info) => {
        if (context.user) {
          return fn(parent, args, context, info);
        }
        throw new Error('User is not authenticated');
      };
    
    /*
    * getLoggedInUser returns the logged in user from the context.
    */
    const getLoggedInUser = (parent, args, context, info) => context.user;
    
    registry.createType(`
      type User {
        id: ID!
        username: String!
      }
    `;
    
    registry.createType(`
      type Query {
        me: User
      }
    `, {
      me: compose(authenticated)(getLoggedInUser)
    };
    
    const schema = new GraphQLSchema({
      query: registry.getType('Query'),
    });
    
    上述代码的工作原理与第一个代码片段完全相同。我们没有在主解析器函数中检查用户,而是创建了一个高度可重用和可测试的中间件函数,实现了相同的功能。这种设计的直接影响可能还不明显,但如果我们想添加另一个受保护的路由并记录解析程序的运行时间,请考虑会发生什么。通过我们的新设计,其简单程度如下:

    const traceResolve =
      (fn: GraphQLFieldResolver) =>
      async (obj: any, args: any, context: any, info: any) => {
        const start = new Date().getTime();
        const result = await fn(obj, args, context, info);
        const end = new Date().getTime();
        console.log(`Resolver took ${end - start} ms`);
        return result;
      };
    
    registry.createType(`
      type Query {
        me: User
        otherSecretData: SecretData
      }
    `, {
      me: compose(traceResolve, authenticated)(getLoggedInUser)
      otherSecretData: compose(traceResolve, authenticated)(getSecretData)
    };
    
    使用此技术将帮助您构建更健壮的GraphQLAPI。函数组合是身份验证任务的一个很好的解决方案,但您也可以使用它记录解析程序、清理输入、处理输出等等

    解决方案#3: 一个不错的解决方案是将数据提取到一个单独的层中,并在那里进行授权检查。 下面是一个遵循上述原则的示例。它用于获取用户可以看到的所有待办事项列表的查询

    对于以下查询

    {
      allLists {
        name
      }
    }
    
    不要这样做:

    //in schema.js (just the essential bits)
    allLists: {
      resolve: (root, _, ctx) => {
        return sql.raw("SELECT * FROM lists WHERE owner_id is NULL or owner_id = %s", ctx.user_id);
      }
    }
    
    // in schema.js (just the essential bits)
    allLists: {
      resolve: (root, _, ctx) => {
        //factor out data fetching
        return DB.Lists.all(ctx.user_id)
          .then( lists => {
            //enforce auth on each node
            return lists.map(auth.List.enforce_read_perm(ctx.user_id) );
          });
      }
    }
    //in DB.js 
    export const DB = {
      Lists: {
        all: (user_id) => {
          return sql.raw("SELECT id FROM lists WHERE owner_id is NULL or owner_id = %s, user_id);
        }
      }
    }
    //in auth.js
    export const auth = {
      List: {
       enforce_read_perm: (user_id) => {
         return (list) => {
           if(list.owner_id !== null && list.owner_id !== user_id){
             throw new Error("User not authorized to read list");
           } else {
             return list;
           }
         }
       }
    }
    
    相反,我建议您这样做:

    //in schema.js (just the essential bits)
    allLists: {
      resolve: (root, _, ctx) => {
        return sql.raw("SELECT * FROM lists WHERE owner_id is NULL or owner_id = %s", ctx.user_id);
      }
    }
    
    // in schema.js (just the essential bits)
    allLists: {
      resolve: (root, _, ctx) => {
        //factor out data fetching
        return DB.Lists.all(ctx.user_id)
          .then( lists => {
            //enforce auth on each node
            return lists.map(auth.List.enforce_read_perm(ctx.user_id) );
          });
      }
    }
    //in DB.js 
    export const DB = {
      Lists: {
        all: (user_id) => {
          return sql.raw("SELECT id FROM lists WHERE owner_id is NULL or owner_id = %s, user_id);
        }
      }
    }
    //in auth.js
    export const auth = {
      List: {
       enforce_read_perm: (user_id) => {
         return (list) => {
           if(list.owner_id !== null && list.owner_id !== user_id){
             throw new Error("User not authorized to read list");
           } else {
             return list;
           }
         }
       }
    }
    
    您可能认为DB.Lists.all函数已经在强制执行权限,但在我看来,它只是试图不获取太多的数据,即permissi