Когато работите върху базиран на graphql API бекенд, използвайки node.js, ако се натъкнете на авторизация, базирана на роли, има много начини за авторизиране на влезлия потребител.

Можете да запазите логиката за оторизация напълно отделно от graphql (в контролер), можете да напишете логиката в самите резолвери (увеличава количеството код в резолверите) или за да запазите кода чист и разбираем, напишете GraphQL персонализирани директиви за схема.

И така, ето как бихте написали персонализирана директива за схема в graphql за разрешаване на определени роли.

//HasRoleDirective.js

import { SchemaDirectiveVisitor } from "apollo-server-express";
import {
  GraphQLDirective,
  DirectiveLocation,
  GraphQLList,
  defaultFieldResolver
} from "graphql";
import { ensureAuthenticated } from "../controllers/authController";
import { AuthorizationError } from "../errors";

class HasRoleDirective extends SchemaDirectiveVisitor {
  static getDirectiveDeclaration(directiveName, schema) {
    return new GraphQLDirective({
      name: "hasRole",
      locations: [DirectiveLocation.FIELD_DEFINITION],
      args: {
        roles: {
          type: new GraphQLList(schema.getType("Role"))
        }
      }
    });
  }
  visitFieldDefinition(field) {
    const { resolve = defaultFieldResolver } = field;
    const roles = this.args.roles;
    field.resolve = async function(...args) {
      const [, , context] = args;
      await ensureAuthenticated(context);
      const userRoles = context.me.role;

      if (roles.some(role => userRoles.indexOf(role) !== -1)) {
        const result = await resolve.apply(this, args);
        return result;
      }
      throw new AuthorizationError({
        message: "You are not authorized for this resource"
      });
    };
  }
}
export default HasRoleDirective;

Първо декларираме името на директивата и валидните аргументи, които тя приема, когато приема аргументи.
по-късно в дефиницията на полето `visitFieldDefinition(field)`, където логиката трябва да бъде описана, приемаме аргументи, извличаме contexrt от аргументите, извикването на функцията `ensureAuthenticated(context)` е да провери jwtToken от контекст, включих ролята на потребителя в jwt токена.
И така, директивата `HasRole` е декларирана и готова за използване. За да използвате, трябва да предадете директивите на вашата конфигурация на graphql и да я декларирате в typeDefinition, както следва

// GraphQL Config
const schemaDirectives = { hasRole: HasRoleDirective };
const server = new ApolloServer({
  typeDefs,
  resolvers,
  schemaDirectives,
  ...context
});

//typedefinitions
import { gql } from "apollo-server-express";
export default gql`
  directive @hasRole(roles: [String!]) on FIELD_DEFINITION | FIELD
  scalar Date

  type Query {
    _: String
  }
  type Mutation {
    _: String
  }
`;

по този начин ще можете да използвате директивата за персонализирана схема във вашия typeDefs
пример за това как да използвате директивата за персонализирана схема:

import { gql } from "apollo-server-express";

export default gql`
  extend type Query {
    businesses: [Business!] @hasRole(roles: [THIS_SUPER_ADMIN])
    business(id: ID!): Business @hasRole(roles: [THIS_ADMIN, THIS_SUPER_ADMIN])
  }
  extend type Mutation {
    businessUpdate(name: String!): Business!
      @hasRole(roles: [THIS_ADMIN, THIS_SUPER_ADMIN])
  }
  type Business {
    id: ID!
    name: String!
  }
`;

Друг пример, за проверка дали даден потребител е удостоверен

//AuthDirective.js
import { SchemaDirectiveVisitor } from "apollo-server-express";
import { defaultFieldResolver } from "graphql";
import { ensureAuthenticated } from "../controllers/authController";

class AuthDirective extends SchemaDirectiveVisitor {
  visitFieldDefinition(field) {
    const { resolve = defaultFieldResolver } = field;

    field.resolve = async function(...args) {
      const [, , context] = args;
      await ensureAuthenticated(context);
      return resolve.apply(this, args);
    };
  }
}
export default AuthDirective;
//passing to graphql config
const schemaDirectives = { auth: AuthDirective };
const server = new ApolloServer({
  typeDefs,
  resolvers,
  schemaDirectives,
  ...context
});

//declaration in typeDefinitions
import { gql } from "apollo-server-express";
export default gql`
  directive @auth on FIELD_DEFINITION
  scalar Date

  type Query {
    _: String
  }
  type Mutation {
    _: String
  }
`;
//typedefinitions usage
`extend type Query {
    payments(
      customer: ID
      status: String
      cursor: String
      limit: Int
    ): PaymentCollection! @auth
  }
  `

Ако трябва да приложите повече от една директива към заявка/мутация ЗАБЕЛЕЖКА: посоката, в която пишете директиви в заявките на graphql, е отдясно наляво, най-дясната директива се разрешава първо, след това тази отляво се разрешава повторно.
така че кажете дали имате това

`extend type Query {
    payments(
      customer: ID
      status: String
      cursor: String
      limit: Int
    ): PaymentCollection! @hasRole(roles: [THIS_ADMIN, THIS_SUPER_ADMIN]) @auth
  }`

директивата auth се разрешава първо и hasRoles се разрешава след директивата auth. Да кажем, че директивата за удостоверяване е неуспешна, директивата hasRoles никога не е достигната.

Все още уча много в GraphQL, ако има някакви подобрения или грешки в кода по-горе, моля, напишете коментар,
винаги е по-добре да се учим от грешките, които правим :)