TL;DR

  • Добавить аутентификацию в приложение Vue.js с Auth0
  • Авторизация с использованием разрешений JWT и Hasura GraphQL
  • Пример приложения Vue, защищенного логином для получения статей, написанных авторизованным пользователем.
  • Исходный код для образца приложения

Стек технологий

Приложение использует следующий стек, который необходимо настроить и настроить для его работы:

Давайте развернем Hasura вместе с postgres, чтобы подготовить наши API GraphQL.

Развернуть Хасуру

Hasura - это движок с открытым исходным кодом, который предоставляет вам API-интерфейсы GraphQL в реальном времени для новых или существующих баз данных Postgres со встроенной поддержкой для сшивания пользовательских API-интерфейсов GraphQL и запуска веб-перехватчиков при изменении базы данных.

Следуйте инструкциям в документации, чтобы развернуть Hasura. Обратите внимание на URL-адрес Heroku для конечной точки GraphQL. Вы будете настраивать это в приложении позже.

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

Теперь бэкэнд готов! Вы сможете мгновенно делать запросы с помощью API Hasura GraphQL. Конечная точка будет выглядеть так (https://myapp.herokuapp.com/v1alpha1/graphql). Мы вернемся к этому во время интеграции с приложением Vue.

Создать приложение в Auth0

  1. Перейдите в Панель управления Auth0 и создайте приложение в виде одностраничного веб-приложения.

2. В настройках приложения добавьте http://localhost:3000/callback как «Разрешенные URL-адреса обратного вызова» и http://localhost:3000 как «Разрешенные веб-источники», чтобы разрешить локальную разработку приложения.

Добавить правила для пользовательских утверждений JWT

На панели инструментов Auth0 перейдите к «Правилам». Добавьте следующие правила, чтобы добавить наши собственные утверждения JWT:

function (user, context, callback) {
  const namespace = "https://hasura.io/jwt/claims";
  context.idToken[namespace] = 
    { 
      'x-hasura-default-role': 'user',
      // do some custom logic to decide allowed roles
      'x-hasura-allowed-roles': user.email === '[email protected]' ? ['user', 'admin'] : ['user'],
      'x-hasura-user-id': user.user_id
    };
  callback(null, user, context);
}

Получите сертификат подписи JWT

Перейдите на https://hasura.io/jwt-config и сгенерируйте конфигурацию для своего домена Auth0.

Скопируйте конфигурацию JWT, созданную для приложения Auth0.

Включить режим JWT на Hasura

Сгенерированную выше конфигурацию необходимо использовать в переменной среды HASURA_GRAPHQL_JWT_SECRET. Нам также нужно установить ключ HASURA_GRAPHQL_ADMIN_SECRET для работы режима JWT.

После того, как вы добавили это, конечные точки GraphQL можно будет запрашивать только с использованием заголовка Authorization или X-Hasura-Admin-Secret.

Создать правило Auth0

Каждый раз, когда пользователь регистрируется на Auth0, нам нужно синхронизировать этого пользователя с нашей базой данных postgres. Это делается с использованием правил Auth0. Создайте еще одно правило и вставьте следующий код:

function (user, context, callback) {
  const userId = user.user_id;
  const nickname = user.nickname;
  
  request.post({
  headers: {'content-type' : 'application/json', 'x-hasura-admin-secret': '<your-admin-secret>'},
  url:     'http://myapp.herokuapp.com/v1alpha1/graphql',
  body:    `{\"query\":\"mutation($userId: String!, $nickname: String) {\\n          insert_users(\\n            objects: [{ auth0_id: $userId, name: $nickname }]\\n            on_conflict: {\\n              constraint: users_pkey\\n              update_columns: [last_seen, name]\\n            }\\n          ) {\\n            affected_rows\\n          }\\n        }\",\"variables\":{\"userId\":\"${userId}\",\"nickname\":\"${nickname}\"}}`
}, function(error, response, body){
    console.log(body);
    callback(null, user, context);
});
}

Замените admin secret и url соответствующим образом.

Наконец-то у нас есть готовая полная бэкэнд и настройка аутентификации. Давайте настроим интерфейс Vue.js для выполнения запроса GraphQL с правильными заголовками.

Настроить Vue-CLI-Apollo-Plugin

Мы будем использовать пример приложения Auth0, чтобы начать работу со стандартным кодом.

Следующая команда генерирует настройку клиента apollo для приложения Vue.

vue add apollo

Это создаст файл с именем vue-apollo.js в src. В этом файле мы будем настраивать объект options, getAuth, определяя следующее:

getAuth: tokenName => {
    // get the authentication token from local storage if it exists
    // return the headers to the context so httpLink can read them
    const token = localStorage.getItem('apollo-token')
    if (token) {
      return 'Bearer ' + token
    } else {
      return ''
    }
},

Эта конфигурация гарантирует, что ApolloClient использует токен, возвращаемый Auth0 для заголовка Authorization, при выполнении запроса или подписки.

Аутентифицированный запрос

Клиент Apollo был настроен с правильными заголовками в приведенной выше настройке. Итак, давайте добавим простой запрос для получения списка статей, написанных пользователем, который вошел в систему.

export default {
  apollo: {
    // Simple query that will update the 'article' vue property
    article: gql`query {
      article {
        id
        title
      }
    }`,
  },
}

Теперь мы хотели бы показать это, только если пользователь вошел в приложение.

Итак, в нашем теге <template> для Home.vue мы будем использовать следующий фрагмент кода для перечисления статей.

<template>
    ...
    ...
    <div v-if="isAuthenticated">
      <h1 class="mb-4">
        Articles written by me
      </h1>
      <div v-for="a in article" :key="a.id">
        {{a.id}}. {{ a.title }}
      </div> 
    </div>
    ...
    ...
</template>

Обратите внимание, что мы гарантируем, что эта разметка должна отображаться только в том случае, если isAuthenticated возвращает true. Чтобы реализовать это, мы генерируем событие после каждого успешного входа в систему.

Перейдите к src/auth/authService.js, чтобы увидеть детали реализации входа в систему Auth0 и генерации событий.

В этом файле событие генерируется после успешного входа в систему.

this.emit(loginEvent, {
  loggedIn: true,
  profile: authResult.idTokenPayload,
  state: authResult.appState || {}
});

plugin был зарегистрирован для обработки этого события в src/plugins

import authService from "../auth/authService";

export default {
  install(Vue) {
    Vue.prototype.$auth = authService;

    Vue.mixin({
      created() {
        if (this.handleLoginEvent) {
          authService.addListener("loginEvent", this.handleLoginEvent);
        }
      },

      destroyed() {
        if (this.handleLoginEvent) {
          authService.removeListener("loginEvent", this.handleLoginEvent);
        }
      }
    });
  }
};

Итак, как только возникает loginEvent, вызывается метод handleLoginEvent.

И в нашем Home.vue компоненте мы обрабатываем этот метод для обновления значения isAuthenticated. По умолчанию это false, и после успешного входа в систему обновляется до true.

methods: {
    handleLoginEvent(data) {
      this.isAuthenticated = data.loggedIn;
      this.isLoading = false;
    }
},

Вышеупомянутый запрос GraphQL отправляется с использованием заголовка токена, возвращаемого Auth0, и это обеспечивает аутентификацию.

Авторизация с помощью JWT

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

Перейдите по URL-адресу приложения Heroku, чтобы открыть консоль Hasura, и перейдите к Данные- ›статья-› Разрешения, чтобы просмотреть разрешения, определенные для роли user.

Проверка разрешений выглядит так:

{ "user_id": {"_eq": "X-Hasura-User-Id"}}

Это означает, что когда запрос отправляется с Authorization: Bearer <token> от клиента, он будет искать значение X-Hasura-User-Id из полезных данных токена и фильтровать его по столбцу user_id, гарантируя, что только зарегистрированные пользователи получают данные, а также получают только их данные. У пользователя есть права доступа ко всем столбцам.

Защищенные маршруты с использованием Vue Router

Поскольку мы используем Vue Router, мы можем добавить Navigation Guards, используя Global Before Guard. Это вызывается всякий раз, когда запускается переход, и переход считается отложенным, пока не будет разрешен.

В src/router.js мы определяем beforeEach охранник, который проверяет логическое значение auth.isAuthenticated() перед разрешением.

router.beforeEach((to, from, next) => {
  if (to.path === "/" || to.path === "/callback" || auth.isAuthenticated()){
    return next();
  }

  auth.login({ target: to.path });
});

В случае, если страница не /, /callback или пользователь не аутентифицирован, то пользователь перенаправляется на страницу входа с использованием метода auth.login.

Запуск приложения

Нам нужно настроить конечную точку Hasura GraphQL в приложении Vue.js. Перейдите к src/vue-apollo.js и соответствующим образом измените значения httpEndpoint и wsEndpoint.

Запустите образец приложения, выполнив следующие команды:

npm install 
npm run serve

Вы должны увидеть такой экран:

Я собрал шаблон, чтобы вы могли быстро приступить к работе!

Проверьте это на гитхабе.

Попробуйте и дайте нам знать, что вы думаете. Если у вас есть какие-либо вопросы или возникнут какие-либо проблемы, не стесняйтесь обращаться к нам в twitter, github или на нашем сервере Discord.

Первоначально опубликовано на https://blog.hasura.io 1 апреля 2019 г.