所以,实际上你想这样做
i) 如果 $authenticationToken 被传递,你想要得到“pointRate”。
ii) 并且您还希望避免在后续中传递 $authenticated
查询。因为您关心您的客户是否可以做出一些贡献
诸如发送经过身份验证的错误是正确的,其中身份验证令牌
为空。
因此,总的来说,我想回答的是,如果您想使用 GraphQL 自己处理身份验证,首先您必须创建一个令牌,然后您必须在每个请求或后续请求中传递该令牌。否则是不可能的。因为未经身份验证,不会提供敏感数据。
另一方面,您可以使用会话身份验证。您可以访问所有数据,直到会话关闭。
如果不满意,您可以阅读以下简短描述以及与您类似的场景。我还尝试积累一些相关的示例解决方案以便更好地理解,它可能会让您更加清晰。
由于GraphQL API是完全公开的,您可以通过两种方式进行身份验证。
-
让 Web 服务器(例如 Express 或 nginx)负责身份验证。
- 在 GraphQL 本身中处理身份验证。
If you 在网络服务器中进行身份验证,您可以使用标准身份验证包(例如,express 的passport.js),并且许多现有的身份验证方法将开箱即用。您还可以根据自己的喜好添加和删除方法,而无需修改 GraphQL 架构。
If 您正在自己实施身份验证,执行以下操作
因为并行执行对于性能来说很重要。突变和查询按照给定的顺序连续执行。
因此,在大多数情况下,最好在 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)
};
使用此技术将帮助您构建更强大的 GraphQL API。函数组合是身份验证任务的一个很好的解决方案,但您也可以使用它来记录解析器、清理输入、调整输出等等。
解决方案#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;
}
}
}
}
您可能认为 DB.Lists.all 函数已经在强制执行权限,但在我看来,它只是试图不获取太多数据,权限本身并不是在每个节点上单独强制执行的。这样,您就可以在一个地方进行身份验证检查,并且可以确保它们将被一致地应用,即使您在许多不同的地方获取数据也是如此。
解决方案#4:
身份验证流程可以通过多种不同方式完成。
i) basic auth,
ii) session auth, or
iii) token auth.
由于您的问题是根据令牌身份验证提出的,因此我想与您见面使用令牌身份验证的 Scaphold。我们所做的一切,无论是将用户登录到 Scaphold 还是将您的用户登录到您的应用程序,我们都使用令牌来管理用户的身份验证状态。身份验证流程的工作原理如下:
a) 用户使用用户名和密码登录。
b) GraphQL 服务器根据用户的散列密码验证数据库中的用户。
c) 如果成功,服务器将返回一个 JSON Web 令牌 (JWT),它是带有到期日期的 Base64 编码令牌。这是身份验证令牌。
d) 要使用身份验证令牌,您将来的请求应在标头中包含身份验证令牌,如下所示
{ 授权:'承载' + [Auth_Token] }
现在,每次服务器(可能是 Node Express)看到标头中的令牌时,它都会解析出令牌并验证它,并在 GraphQL 世界中将识别出的用户保存在上下文中,以便在应用程序的其余部分中使用。用户现已登录。
有关更多信息,您可以在本教程中了解有关 @include 的更多信息:https://github.com/mugli/learning-graphql/blob/master/4.%20Querying%20with%20Directives.md#include https://github.com/mugli/learning-graphql/blob/master/4.%20Querying%20with%20Directives.md#include
要逐步学习 graphql 身份验证,您可以阅读本教程:GraphQL 身份验证 https://www.howtographql.com/graphql-js/5-authentication/
资源链接:
- 身份验证用
GraphQL https://scaphold.io/community/blog/authentication-in-graphql/
- 身份验证指南
GraphQL https://dev-blog.apollodata.com/a-guide-to-authentication-in-graphql-e002a4039d1
- GraphQL 的最佳实践
安全 https://scaphold.io/community/questions/graphql-security-best-practices/