Аутентификация и управление доступом C ретранслятором
официальная линия от Facebook является то, что реле "намеренно агностик о механизмах аутентификации."Во всех примерах в репозитории ретрансляции аутентификация и контроль доступа являются отдельной проблемой. На практике я не нашел простого способа реализовать это разделение.
примеры, приведенные в репозитории ретрансляции, имеют корневые схемы с viewer
поле, которое предполагает наличие одного пользователя. И этот пользователь имеет доступ к всё.
однако на самом деле приложение имеет много пользователей, и каждый пользователь имеет разные степени доступа к каждому узлу.
Предположим, у меня есть эта схема в JavaScript:
export const Schema = new GraphQLSchema({
query: new GraphQLObjectType({
name: 'Query',
fields: () => ({
node: nodeField,
user: {
type: new GraphQLObjectType({
name: 'User',
args: {
// The `id` of the user being queried for
id: { type: new GraphQLNonNull(GraphQLID) },
// Identity the user who is querying
session: { type: new GraphQLInputObjectType({ ... }) },
},
resolve: (_, { id, session }) => {
// Given `session, get user with `id`
return data.getUser({ id, session });
}
fields: () => ({
name: {
type: GraphQLString,
resolve: user => {
// Does `session` have access to this user's
// name?
user.name
}
}
})
})
}
})
})
});
некоторые пользователи являются полностью частными с точки зрения запрашивающего пользователя. Другие пользователи могут предоставлять только определенные поля в запросах пользователей. Таким образом, чтобы получить пользователя, клиент должен не только предоставить идентификатор пользователя, к которому он обращается, но и идентифицировать себя так что контроль доступа может произойти.
это, кажется, быстро усложняется, поскольку необходимость контролировать доступ стекает по графику.
кроме того, мне нужно контролировать доступ для каждого корневого запроса, например nodeField
. Мне нужно убедиться, что каждый узел реализует nodeInterface
.
все это кажется слишком однообразной работы. Существуют ли какие-либо известные модели для упрощения этого? Я думаю об этом неправильно?
3 ответов
различные приложения имеют очень разные требования к форме управления доступом, поэтому выпечка чего-то в базовую релейную структуру или справочную реализацию GraphQL, вероятно, не имеет смысла.
подход, который я видел довольно успешным, заключается в том, чтобы испечь конфиденциальность/контроль доступа в модель данных/фреймворк загрузчика данных. Каждый раз, когда вы загружаете объект, вы не просто загрузить его по ID, но и для зрителя. Если зритель не видит объект, он не сможет загрузить как будто его не существует чтобы предотвратить даже утечку существования объекта. Объект также сохраняет контекст просмотра, и некоторые поля могут иметь ограниченный доступ, который проверяется перед возвращением из объекта. Выпечка этого в механизме загрузки данных нижнего уровня помогает гарантировать, что ошибки в коде продукта / GraphQL более высокого уровня не утечка частных данных.
в конкретном примере мне может быть не разрешено видеть какого-либо пользователя, потому что он заблокировал меня. Вам может быть разрешено видеть его в целом, но нет его электронной почты, так как вы с ним не друзья.
в коде что-то вроде этого:
var viewer = new Viewer(getLoggedInUser());
User.load(id, viewer).then(
(user) => console.log("User name:", user.name),
(error) => console.log("User does not exist or you don't have access.")
)
попытка реализовать видимость на уровне GraphQL имеет большой потенциал для утечки информации. Подумайте о многих способах доступа к пользователю в реализации GraphQL для Facebook:
node($userID) { name }
node($postID) { author { name } }
node($postID) { likers { name } }
node($otherUserID) { friends { name } }
все эти запросы могут загружать имя пользователя, и если пользователь заблокировал вас, ни один из них не должен вернуть пользователя или его имя. Наличие контроля доступа ко всем этим полям и не забывание проверки в любом месте-это рецепт отсутствия проверки где-то.
я обнаружил, что обработка аутентификации проста, если вы используете GraphQL rootValue
, который передается в механизм выполнения при выполнении запроса по схеме. Это значение доступно на всех уровнях выполнения и полезно для хранения маркера доступа или того, что идентифицирует текущего пользователя.
если вы используете express-graphql промежуточное ПО, вы можете загрузить сеанс в промежуточное ПО, предшествующее промежуточному по GraphQL, а затем настройте промежуточное ПО GraphQL, чтобы поместить этот сеанс в корневое значение:
function getSession(req, res, next) {
loadSession(req).then(session => {
req.session = session;
next();
}).catch(
res.sendStatus(400);
);
}
app.use('/graphql', getSession, graphqlHTTP(({ session }) => ({
schema: schema,
rootValue: { session }
})));
этот сеанс затем доступен на любой глубине в схеме:
new GraphQLObjectType({
name: 'MyType',
fields: {
myField: {
type: GraphQLString,
resolve(parentValue, _, { rootValue: { session } }) {
// use `session` here
}
}
}
});
вы можете связать это с загрузкой данных, "ориентированных на просмотр" для достижения контроля доступа. Проверьтеhttps://github.com/facebook/dataloader который помогает создать этот вид объекта загрузки данных и обеспечивает пакетирование и кэширование.
function createLoaders(authToken) {
return {
users: new DataLoader(ids => genUsers(authToken, ids)),
cdnUrls: new DataLoader(rawUrls => genCdnUrls(authToken, rawUrls)),
stories: new DataLoader(keys => genStories(authToken, keys)),
};
}
Если у кого есть проблемы с этой темой: я сделал пример РЕПО для ретрансляции/GraphQL / экспресс-аутентификации на основе ответа dimadima. Он сохраняет данные сеанса (идентификатор пользователя и роль) в файле cookie с использованием промежуточного программного обеспечения express и мутации GraphQL