Рубрики
Без рубрики

JWT с рельсами, колдовством и ангулярными

Узнайте, как добавить JWT на рельсы и кодовую базу Angularjs.

Автор оригинала: Adrian Perez.

Jwt (Короче говоря для JSON Web Token ) – это компактное безопасное уверенное в URL-средствах представления претензий, которые должны быть переданы между двумя сторонами, как определено стандартом. Обычно используется для аутентификации, и недавно выступают в соответствии с классической схемой Cookie в приложениях «Ограниченные страницы» (SPAS).

Хотя печенье и аутентификация на стороне серверов являются наиболее установленными решениями, для API обычно лучшие альтернативы OAUTH2 и jwt.

Этот пост предполагает некоторое уровне знакомства, но должен быть легко следовать, посещение домашней страницы, в котором я связался, прежде чем достаточно для большинства образцов кода, если вы хотите, чтобы я сделал рекомендацию ресурсов, чтобы выкопать глубже, вы можете проверить блога INNIDEA пост , еще один по Toptal (он сосредоточен на Laravel, но вводной раздел вступительный раздел это стоит прочитать), или если вы хотите пройти все, что вы проверяете это Плюсульский курс На OAUTH2, OpenID Connect и JWT.

Давайте рассмотрим, как добавить JWT на рельсы и Angularjs CodeBase.

Реализация JWT на стороне сервера

Мы будем смотреть, как реализовать доступ к аутентификации и доступу ресурсов API к Ruby on Rails.

Быстрые заметки о приложении и API

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

Он использует Виноград API Framework, представимы Для модели сериализации (ведущего) и несколько особенностей проекта шахты называются Радриар Отказ Кроме того, хотя код должен быть довольно автономным, есть несколько вещей, которые стоит заметить:

  • #represent и #represent_each На конечных точках винограда: Радриар добавляет этот метод, который, помимо прочего, и использует рефинер для модели или рассматриваемой коллекции.
  • Репрезеры являются основной особенностью представимы Этот пост не включает их, поскольку они довольно базовые и на самом деле ничего не добавляют к теме.
  • База API SuperClass: базовый класс для всех версительных API конечных точек, включает в себя Стойку:: conditalget и Стойка:: Etag промежуточное программное обеспечение, в основном из-за премежности от примеров – кэширование с Гарнер Отказ

Добавление зависимостей

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

В Gemfile :

gem 'sorcery'
gem 'validates_email_format_for'
gem 'jwt'

Использование Sorcery для добавления аутентификации

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

Я знаю много пользователей, клянутся Разработать , но главное преимущество библиотеки, которую мы выбрали, это то, что Мы Полностью отвечали за определение потока аутентификации, что имеет смысл, поскольку мы создаем один, который будет использовать JWT. Вообще говоря, я нашел разработку, чтобы быть немного переплетенным, когда у вас есть сервер только для API, и, конечно, они другие причины и сценарии, которые вы можете использовать воспользоваться колдовством вместо того, чтобы придумать, или наоборот, но они за пределами объема этого поста.

Начнем с модификации нашего Пользователь Модель для проволоки его с аутентификация_with_sorcery! линия:

В Приложение/Модели/user.rb :

class User
  include Mongoid::Document
  authenticates_with_sorcery!
      
  validates :username, presence: true, uniqueness: true
  validates :password, length: { minimum: 6 }, on: :create
  validates :email, uniqueness: true, email_format: true
end

Это все, что нужно, чтобы включить поведение аутентификации для этого класса. Также обратите внимание на использование Validates_email_format_of Для проверки электронной почты это гарантирует, что формат электронной почты действителен (RFC-действительный), и это также настраивается.

Avid Reader может заметить, что мы пропустим какие-либо поле Декларации, это потому, что наша модель использует только те, которые уже предоставлены сами библиотекой.

Включение аутентификации в API

Конечно, мы не могли остановиться там, потому что сами модели нужны способ получить доступ с внешнего мира и контролироваться, следовательно, нам нужно погрузиться в наш слой API (который может быть просто слоем контроллера, если вы не используете API Framework).

Мы нуждаемся в следующих частях:

  1. Компонент, который создаст и проверяет токены.
  2. Способ для обеспечения аутентификации и восстановления текущего пользователя в API.
  3. Способ «войти» пользователей в.

Реализация функциональности CORE JWT

Токенпровидер Это универсальная служба, которая будет нести ответственность за проверку и создание токенов, которые мы будем использовать для пользователей нашего приложения, используя jwt Библиотека, которую мы добавили в качестве зависимости. Для динамической части токена мы собираемся сохранить его просто и использовать приложение Rails Секрет сам.

Сама сервис отделен, и мы собираемся представить детали реализации на слое API (например, user_id , больше на что позже).

В Приложение/Услуги/Token_Provider.rb :

module TokenProvider
  class << self
    def issue_token(payload)
      JWT.encode(payload, Rails.application.secrets.secret_key_base)
    end
        
    def valid?(token)
      begin
        JWT.decode(token, Rails.application.secrets.secret_key_base)
      end
    end
  end
end

Добавление помощника аутентификации на наше API

При минимальном минимум для любого решения аутентификации, хотя семантика может варьироваться, нам нужны два метода, которые будут доступны: #current_user и # Совершенствование Отказ

Мы собираемся создать модуль помощника по аутентификации, который предоставит функциональность, которые будут включены наши определения API, чтобы сохранить вещи сухими. Это имеет до_акция методы аутентификации, проверка токена и т. Д.

Хотя это может быть не сразу очевидно (не волнуйтесь, это приведет к тому, что мы введем в него часть клиента), обратите внимание, что токен, как ожидается, будет прошедшим через Авторизация Параметр запроса в виде Носитель <Токен> Отказ

В Приложение/API/Blog/v1/auth.rb :

module Blog::V1::Auth
  def validate_token!
    begin
      TokenProvider.valid?(token)
    rescue
      error!('Unauthorized', 401)
    end
  end
  
  def authenticate!
    begin
      payload, header = TokenProvider.valid?(token)
      @current_user = User.find_by(id: payload['user_id'])
    rescue
      error!('Unauthorized', 401)
    end
  end
  
  def current_user
    @current_user ||= authenticate!
  end
  
  def token
    request.headers['Authorization'].split(' ').last
  end
end

В Приложение/API/BLOG/API_V1.RB :

helpers Blog::V1::Auth

Регистрация пользователя и вход

Как часть нашего API, мы собираемся включить регистрацию, поэтому нам нужна правильная конечная точка для создания новых пользователей. Мы также собираемся понадобиться «логин», который на самом деле является обменным потоком от учетных данных пользователя к JWT, что может выглядеть знакомым, если вы знакомы с OAUTH2.

Мы также расширяем пользовательский представитель для этого конкретного запроса в систему с вновь сгенерированным токеном, вот где мы Выпуск токен.

В Приложение/API/Blog/V1/users.rb :

module Blog::V1
  class Users < Base
    helpers do
      def represent_user_with_token(user)
        represent(user).merge(token: ::TokenProvider.issue_token(
          user_id: user.id
        ))
      end
    end
    
    resource :users do
      params do
        requires :username
        requires :email
        requires :password
      end
      post do
        user = User.new(declared(params))
        user.save!
        represent_user_with_token(user)
      end
      
      params do
        requires :email
        requires :password
      end
      post "login" do
        user = User.find_by(email: params[:email])
        if user = User.authenticate(params[:email], params[:password])
          represent_user_with_token(user)
        else
          error!("Invalid email/password combination", 401)
        end
      end
    end
  end
end

Обеспечение ресурсов и использование данных Scoped пользователем

Теперь, когда у нас есть наша аутентификация, реализованная на уровне модели и контроллера, нам нужно только использовать их в наших конкретных конечных точках.

Поскольку они являются наиболее распространенными сценариями, в этом – Albeit Contrement-пример, посты могут быть доступны только в случае аутентификации и возвращаемой коллекции, заключается в том, что только для постов, принадлежащих аутентифицированному пользователю.

Наше определение конечного точка будет выглядеть следующее:

В Приложение/API/BLOG/V1/POSTS.RB :

module Blog::V1
  class Posts < Base
    before do 
      authenticate!
    end
    
    desc "Get a list of my awesome posts"
    get do
      represent_each current_user.posts.ordered
    end
  end
end

И это было бы все, что нужно для очень простой реализации JWT сверху вниз на сервере.

Давайте прыгнем до потребителя этого интерфейса или просто «Клиент» Отказ

Реализация JWT на стороне клиента

На угловой стороне вещей наше прохождение реализации потребуется немного другой подход и порядок, мы начнем с компонентов сначала внизу.

Добавление службы аутентификации

Нам нужно определить службу аутентификации для репликации одинаковой аутентификации и текущей пользовательской функциональности, которую мы описали как фундаментальные в разделе «Сервер», чтобы убедиться, что мы можем получить доступ к аутентифицированным ресурсам, договориться о токене с сервером и правильно отвечать на ошибки.

Добавление запроса/перехвата ответа

В основе нашей реализации является $ http Перехватчик, это гарантирует, что мы добавим токен к каждому запросу, а также перенаправить для входа в систему в случае несанкционированных или неавторизованных сообщений.

В SRC/App/Marross.js :

  $httpProvider.interceptors.push('AuthInterceptor'); 

В SRC/Компоненты/auth/auth.interceptor.js :

(function() {
  'use strict';

  function AuthInterceptor($q, $injector) {
    return {
      request: function(config) {
        var LocalService = $injector.get('LocalService');
        var token;

        if (LocalService.get('auth_token')) {
          token = LocalService.get('auth_token');
        }

        if (token) {
          config.headers.Authorization = 'Bearer ' + token;
        }

        return config;
      },
      responseError: function(response) {
        var LocalService = $injector.get('LocalService');
        // TODO: revisit for the 403
        if (response.status === 401 || response.status === 403) {
          LocalService.unset('auth_token');
          $injector.get('$state').go('login');
        }

        return $q.reject(response);
      }
    }
  }

  AuthInterceptor.$inject = ['$q', '$injector'];

  angular.module('blog.auth').factory('AuthInterceptor', AuthInterceptor);
})();

МетальСервис просто обертка к LocalStorage API браузера, и оно должно быть довольно неясно, даже не видя его реализации.

Добавление службы авторизации

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

Давайте начнем с проверки, если пользователь аутентифицирован, учитывая, как наш перехватчик был реализован, это довольно легко:

В SRC/Компоненты/auth/auth.service.js :

function Auth($http, LocalStorageService, API_URL)) {
  return {
    isAuthenticated: function() {
      return LocalStorageService.get('auth_token');
    }
    // ...
}

Auth.$inject = ['$http', 'LocalService', 'API_URL', '$rootScope'];

angular.module("blog.auth").factory("Auth", Auth);

Для остальной части этого раздела мы не будем повторять имя файла или код, который мы только что показали, как оно одинаково.

Войти и выйти в систему довольно просты.

{
  // ...
  login: function(credentials) {
    var login = $http.post(API_URL + '/login', credentials);
      login.success(function(result) {
        LocalStorageService.set('auth_token', result.token);
        var user = { 
          id: result.id, 
          username: result.username,
          avatarUrl: result.avatarUrl
        }
        LocalService.set('user', JSON.stringify(user)); 
      });
      
      return login;
  },
  logout: {
    LocalService.unset('auth_token');
    LocalService.unset('user');
  }

Технически, только auth_token Строго необходим, но вы можете увидеть здесь, как его можно использовать, чтобы также хранить сам объект пользователя.

Наконец, давайте посмотрим на функцию регистрации:

{
  // ...
  register: function(formData) {
    LocalService.unset('auth_token');
    var register = $http.post(API_URL + '/users', formData);
    register.success(function(result) {
      LocalService.set('auth_token', result.token);
    });
    
    return register;
  }
}

Реализация контроллеров

В SRC/Компоненты/AUTH/Регистрация .Controller.js :

(function() {
  'use strict';
  
  function Registrations(Auth, $state, $scope) {
    var vm = this;
    vm.errors = [];
  
    vm.register = function() {
      if ($scope.registerForm.$valid) {
        Auth.register(vm.user).then(function() {
          $state.go('posts.list');
        }, function(err) {
          vm.errors.push(err);
        });
      }
    };
  }
  // ...
  Registrations.$inject = ['Auth', '$state', '$scope'];

  angular.module('blog.auth').controller('Registrations', Registrations);
})();

Контроллер входа в систему – (почти тревога) похожий:

В SRC/Компоненты/auth/logins.controller.js :

function Logins($scope, $state, Auth) {
  // ...
  vm.login = function() {
    if ($scope.loginForm.$valid) {
      vm.errors = [];
      Auth.login(vm.user).success(function() {
        $state.go('posts.list');
      }).error(function(err) {
        vm.errors.push(err);
      });
     // ...
  }
}