Аутентификация и контроль доступа с реле
Официальная линия от Facebook заключается в том, что Relay "намеренно не зависит от механизмов аутентификации." Во всех примерах репозитория Relay особое внимание уделяется проверке подлинности и контролю доступа. На практике я не нашел простого способа реализовать это разделение.
В примерах, предоставленных в репозитории ретрансляции, есть корневые схемы с полем viewer
, предполагающим наличие одного пользователя. И у этого пользователя есть доступ ко всему.
Однако на самом деле приложение имеет много пользователей, и каждый пользователь имеет разные степени доступа к каждому node.
Предположим, что у меня есть эта схема в 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
. Мне нужно убедиться, что каждый node реализующий nodeInterface
.
Все это кажется много повторяющейся работой. Существуют ли какие-либо известные шаблоны для упрощения этого? Я думаю об этом неправильно?
Ответы
Ответ 1
Я обнаружил, что обработка аутентификации легко, если вы используете 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)),
};
}
Ответ 2
Различные приложения имеют очень разные требования к форме контроля доступа, поэтому выпекание чего-то в базовую структуру ретрансляции или эталонную реализацию 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 } }
Все эти запросы могут загружать имя пользователя, и если пользователь заблокировал вас, ни один из них не должен возвращать пользователя или его имя. Наличие контроля доступа на всех этих полях и не забывание проверки в любом месте - это рецепт отсутствия пропущенного чек.
Ответ 3
Если у кого-то есть проблемы с этой темой: я сделал пример repo для аутентификации Relay/GraphQL/express на основе ответа dimadima. Он сохраняет данные сеанса (userId и роль) в файле cookie с использованием прямого промежуточного программного обеспечения и мутации GraphQL