Служба аутентификации с Apollo Gateway

Я разрабатываю приложение на основе микросервисов с использованием Apollo Gateway. Каждая служба написана на Node.js и использует Graphql для построения федеративной схемы. Я также использую Mongoose для взаимодействия с базой данных MongoDB, совместно используемой службами. Основная цель разработки этого приложения - изучить и получить опыт использования новых для меня инструментов и технологий, таких как Graphql, микросервисы и Node.js.

У меня вопрос по аутентификации. Я решил использовать аутентификацию на основе JWT с дополнительными сеансами в базе данных для каждого пользователя. Таким образом, я могу отслеживать активные сеансы для каждого пользователя и отменять доступ, отключив сеанс, связанный с токеном. Все это управляется службой Auth, которая отвечает за аутентификацию, создание новых пользователей и функции входа / выхода. Служба Auth предоставляет одну конечную точку REST для проверки токена jwt следующим образом.

...
app.post('verify', async (req, res, next) => {
  const token = req.body.jwt;

  if(!token) {
    res.status(403).send({ error: 'No token provided.' });
  }

  const decoded = jwt.verify(token, process.env.JWT_SECRET);
  const user = await User.findOne({ _id: decoded.sub});

  if (!user) {
    res.status(401).send({ error: 'No user found.' });
  }

  const session = await Session.findOne({
    '_id': {
      $in: user.sessions
    }
  });
  if(!session) {
    res.status(401).send({ error: 'Session not found or expired.' });
  }
  if(!session.valid) {
    res.status(401).send({ error: 'Session not valid.' });
  }

   res.send({
    userId: user.id,
    scopes: user.scopes
  });
}

const server = new ApolloServer({
  schema: buildFederatedSchema([
    {
      typeDefs,
      resolvers
    }
  ]),
  context: ({ req }) => {
    return {
      // headers
      userId: req.get['user-id'] || 0,
      scopes: req.get['user-scopes'] ? req.get['user-scopes'].split(',') : [],
      // Mongoose models
      models: {
        User,
        Session
      }
    }
  }
});

server.applyMiddleware({ app, cors: false });
...

Мой шлюз API основан на шлюзе Apollo для построения федеративной схемы. Аутентификация проверяется службой Auth и передается всем остальным службам через заголовки запросов, установленные шлюзом.

...
// Set authenticated user id in request for other services
class AuthenticatedDataSource extends RemoteGraphQLDataSource {
  willSendRequest({ request, context }) {
    // pass the user's id from the context to underlying services
    // as a header called `user-id`
    request.http.headers.set('user-id', context.userId);
    request.http.headers.set('user-scopes', context.scopes.join(','));
  }
}

const gateway = new ApolloGateway({
  serviceList: [
    { name: 'auth', url: 'https://auth:4000' }
  ],
  buildService: ({ name, url }) => {
    return AuthenticatedDataSource({ url });
  }
});

// Apollo server middleware - last applied
const server = new ApolloServer({
  gateway,
  // not supported
  subscriptions: false,
  context: async ({ req }) => {
    try {
      // Send auth query to Auth service REST api
      const response = await axios.post('https://auth:4000/verify', {
        jwt: req.cookies['plottwist_login']
      });
      // save auth data in context
      return {
        userId: response.data.userId,
        scopes: response.data.scopes
      }
    } catch(e) {
       // deal with error
    }
  }
});

server.applyMiddleware({ app, path, cors: false });
...

Таким образом, поток будет следующим:

  • Шлюз API получает запрос запроса Graphql от клиента.
  • Шлюз API запрашивает у службы аутентификации аутентификацию пользователя с использованием единственной конечной точки REST, предлагаемой службой аутентификации (копирование маркера cookie из полученного запроса).
  • Сервис аутентификации аутентифицирует пользователя и отправляет данные обратно.
  • Шлюз получает ответ, создает дополнительные заголовки запроса и продолжает работу, управляя исходным запросом Graphql.

Это связано с затратами на дополнительный вызов, который шлюз выполняет перед управлением каждым запросом Graphql, поступающим от клиента. Интересно, жизнеспособен ли это вариант или в моих рассуждениях есть серьезные недостатки.

0 ответов

Другие вопросы по тегам