Open4
GraphQLの認証認可、どこでやるのがいいのか?
スクラッチ
Apollo Serverのページでも紹介されているが、要はcontextにuserの情報を詰めてresolverで判定する。Query ResolverでもField Resolverでも同じ
users: (parent, args, context) => {
if (!context.user || !context.user.roles.includes('admin')) return null;
return context.models.User.getAll();
}
@auth Custom Directive
カスタムディレクティブを使う方法。スクラッチ実装よりも宣言的でクリーンな気はする。この場合スキーマに認証認可ルールが露出する。
const typeDefs = `
directive @auth(requires: Role = ADMIN) on OBJECT | FIELD_DEFINITION
enum Role {
ADMIN
REVIEWER
USER
}
type User @auth(requires: USER) {
name: String
banned: Boolean @auth(requires: ADMIN)
canPost: Boolean @auth(requires: REVIEWER)
}
`
Graphql Shield
APIが完結でわかりやすい
const permissions = shield({
Query: {
frontPage: not(isAuthenticated),
fruits: and(isAuthenticated, or(isAdmin, isEditor)),
customers: and(isAuthenticated, isAdmin),
},
Mutation: {
addFruitToBasket: isAuthenticated,
},
Fruit: isAuthenticated,
Customer: isAdmin,
})
schema = applyMiddleware(schema, permissions)
ルールの例。ctxに詰めた情報でbooleanを返すだけ。
const isAdmin = rule()(async (parent, args, ctx, info) => {
return ctx.user.isAdmin
})
envelopでuseGenericAuthを使う
3パターンくらいの実装ができるが、authディレクティブを使うならこの組み合わせは良さそう
import { envelop } from '@envelop/core';
import { useGenericAuth, resolveUser, ValidateUserFn } from '@envelop/generic-auth';
type UserType = {
id: string;
};
const resolveUserFn: ResolveUserFn<UserType> = async context => {
/* ... */
};
const validateUser: ValidateUserFn<UserType> = async (user, context) => {
/* ... */
};
const getEnveloped = envelop({
plugins: [
// ... other plugins ...
useGenericAuth({
resolveUser,
validateUser,
mode: 'protect-auth-directive',
}),
],
});
mode: 'protect-auth-directive'を設定すると、authディレクティブを使うフィールドにはvalidateUser関数を適用するようになる。
他に、validateUserを全フィールドに適用するprotect-all、何もせずにUserをinjectするだけのresolve-onlyがある。
authディレクティブを使う場合のvalidateUser:
import { ValidateUserFn } from '@envelop/generic-auth';
const validateUser: ValidateUserFn<UserType> = async (user, context, { root, args, context, info }, directiveNode) => {
if (!user) {
throw new Error(`Unauthenticated!`);
}
const valueNode = authDirectiveNode.arguments.find(arg => arg.name.value === 'role').value as EnumValueNode;
const role = valueNode.value;
if (role !== user.role) {
throw new Error(`No permissions!`);
}
};