Сервер Apollo - применение аутентификации к определенным преобразователям разрешений только с помощью Passport-JWT

В настоящее время у меня есть серверная часть Node.js, на которой запущен Express с Passport.js для аутентификации, и я пытаюсь переключиться на GraphQL с Apollo Server. Моя цель - реализовать ту же аутентификацию, которую я использую в настоящее время, но не могу понять, как оставить одни резолверы общедоступными, включив авторизацию для других. (Я попытался тщательно изучить этот вопрос, но пока не смог найти подходящего решения.)

Вот мой код в его нынешнем виде:

Моя стратегия JWT:

const opts = {};
opts.jwtFromRequest = ExtractJwt.fromAuthHeaderAsBearerToken();
opts.secretOrKey = JWT_SECRET;

module.exports = passport => {
  passport.use(
    new JwtStrategy(opts, async (payload, done) => {
      try {
        const user = await UserModel.findById(payload.sub);
        if (!user) {
          return done(null, false, { message: "User does not exist!" });
        }
        done(null, user);
      } catch (error) {
        done(err, false);
      }
    })
  );
}

Моя конфигурация server.js и Apollo: (в настоящее время я извлекаю токен-носитель из заголовков HTTP и передаю его своим распознавателям с помощью объекта контекста):

const apollo = new ApolloServer({
  typeDefs,
  resolvers,
  context: async ({ req }) => {
    let authToken = "";

    try {
      if (req.headers.authorization) {
        authToken = req.headers.authorization.split(" ")[1];
      }
    } catch (e) {
      console.error("Could not fetch user info", e);
    }

    return {
      authToken
    };
  }
});

apollo.applyMiddleware({ app });

И, наконец, мои резолверы:

exports.resolvers = {
  Query: {
    hello() {
      return "Hello world!";
    },
    async getUserInfo(root, args, context) {
      try {
        const { id } = args;
        let user = await UserModel.findById(id);
        return user;
      } catch (error) {
        return "null";
      }
    },
    async events() {
      try {
        const eventsList = await EventModel.find({});
        return eventsList;
      } catch (e) {
        return [];
      }
    }
  }
};

Моя цель - оставить некоторые запросы, такие как первый ("привет"), общедоступными, а остальные ограничить запросами только с действительными токенами-носителями. Однако я не уверен как реализовать эту авторизацию в преобразователях с использованием Passport.js и Passport-JWT в частности (обычно это делается путем добавления промежуточного программного обеспечения к определенным конечным точкам, однако, поскольку у меня будет только одна конечная точка (/ graphql) в этом Например, этот параметр ограничит все запросы только аутентифицированными пользователями, что не является тем, что я ищу. Мне нужно каким-то образом выполнить авторизацию в преобразователях, но я не знаю, как это сделать с помощью инструментов, доступных в Passport.js.)

Любые советы высоко ценится!


person Gabor Szekely    schedule 01.02.2019    source источник


Ответы (1)


Я бы создал директиву схемы для авторизованного запроса по определению поля, а затем использовал бы эту директиву везде, где я хочу применить авторизацию. Образец кода :

class authDirective extends SchemaDirectiveVisitor {
    visitObject(type) {
        this.ensureFieldsWrapped(type);
        type._requiredAuthRole = this.args.requires;
    }

    visitFieldDefinition(field, details) {
        this.ensureFieldsWrapped(details.objectType);
        field._requiredAuthRole = this.args.requires;
    }

    ensureFieldsWrapped(objectType) {
        // Mark the GraphQLObjectType object to avoid re-wrapping:
        if (objectType._authFieldsWrapped) return;
        objectType._authFieldsWrapped = true;

        const fields = objectType.getFields();

        Object.keys(fields).forEach(fieldName => {
            const field = fields[fieldName];
            const {
                resolve = defaultFieldResolver
            } = field;
            field.resolve = async function (...args) {
                // your authorization code 
                return resolve.apply(this, args);
            };
        });
    }

}

И объявите это в определении типа

directive @authorization(requires: String) on OBJECT | FIELD_DEFINITION

директива схемы карты в вашей схеме

....
resolvers,
schemaDirectives: {
authorization: authDirective
}

Затем используйте его на своей конечной точке api или любом объекте

Query: {
    hello { ... }
    getuserInfo():Result @authorization(requires:authToken) {...} 
    events():EventResult @authorization(requires:authToken) {...} 
  };
person Amit Bhoyar    schedule 01.02.2019
comment
Отлично, спасибо! Я попробую сегодня днем. - person Gabor Szekely; 01.02.2019
comment
Кроме того, могу ли я просто вставить свой код стратегии JWT в класс authDirective (где вы // написали свой код авторизации)? Разве это не проблема, поскольку этот код предназначен для работы в качестве промежуточного программного обеспечения Express? - person Gabor Szekely; 01.02.2019
comment
@GaborSzekely, Не уверен насчет стратегии JWT, но я считаю, что вы можете вызвать любой другой метод из этого места? Отправьте сообщение, если вы столкнетесь с какой-либо проблемой. - person Amit Bhoyar; 02.02.2019
comment
Не могли бы вы помочь мне понять, где именно я должен объявить authToken или как директива схемы получит его? Мне нужно было бы декодировать его из заголовков HTTP, но как это передать в GraphQL? - person Gabor Szekely; 04.02.2019
comment
Если он присутствует в заголовке, он будет напрямую доступен для метода visitfielddefinition () viar args. Ссылка на этот hackernoon.com/ . у него есть именно то, что вы хотите. - person Amit Bhoyar; 04.02.2019