Skip to main content

JWT авторизация на Angular Js

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

В этой статье я опишу полноценное решение по авторизации на основе JSON Web Token (JWT) на Angular JS, используя Fake Back End для проверки пользователя. (кроме того этот способ можно использовать для устаревшей cookie авторизации, чего я настоятельно не рекомендую делать)

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

В статье будет рассмотрено полноценное решение по авторизации с использованием:

  • Angular Js
  • Fake Back End
  • Bootstrap Css Framework (для быстрой верстки)

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

И так начнем со структуры проекта!

Я писал довольно таки много разных проектов на разных фреймворках но в Angular Js я бы рекомендовал использовать структуру «John Papa’s«, к примеру я положил настройки и роуты в файл App.js, а не в отдельные файлы, поскольку тут не так уж и много кода, и при расширение проекта их можно разделить на разные файлы.

Так же я люблю разделять директивы, сервисы и контроллеры по разным папкам давая им имя с префиксом «app», но вы можете сделать так как Вам удобно:

# Fake — Backend

Разобравшись со структурой давайте начнем с написания нашего фальшивого бэкенда для проверки пользователей. Он позволит нам авторизовать пользователя без настоящего API. Вместо данного примера вы можете использовать ваш API (к примеру любой WordPress c использованием JWT плагина для авторизации)

Cам файл будет выглядеть вот так:

 
 
(function () {
    'use strict'; 
 
    angular
        .module('app')
        .run(setupFakeBackend);
 
    // Настраиваем наш фальшивый бэкенд
    function setupFakeBackend($httpBackend) {
        var testUser = { username: 'test', password: 'test', firstName: 'Test', lastName: 'User' };
 
        // здесь делаем запрос на нашу фальшиваю контрольную точку 
        $httpBackend.whenPOST('/api/authenticate').respond(function (method, url, data) {
            // получаем  параметры из запроса
            var params = angular.fromJson(data);
 
            // проверяем пользовательские данные и если все хорошо отправляем JWT заголовок
            if (params.username === testUser.username && params.password === testUser.password) {
                return [200, { token: 'fake-jwt-token' }, {}];
            } else {
                return [200, {}, {}];
            }
        });

        $httpBackend.whenGET(/^\w+.*/).passThrough();
    }
})();

 

# Cервис для Авторизации

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

Файл будет выглядеть вот так вот:

 
 
(function () {
    'use strict';
 
    angular
        .module('app')
        .factory('AuthenticationService', Service);
 
    function Service($http, $localStorage) {
        var service = {};
 
        service.Login = Login;
        service.Logout = Logout;
 
        return service;
 
        function Login(username, password, callback) {
            $http.post('/api/authenticate', { username: username, password: password })
                .success(function (response) {
                    // Авторизация пройдет если в запросе мы получим авторизационный токен
                    if (response.token) {
                        // Сохраняем данные пользователя в локальное хранилище браузера и оставляем его таким если он перезагрузится или сменится страница
                        $localStorage.currentUser = { username: username, token: response.token };

 						// Добавляем jwt token в авторизационный заголовок для всех запросов
                        $http.defaults.headers.common.Authorization = 'Bearer ' + response.token;
 
                        // отправляем кэлбек при удачной авторизации для подтверждения авторизации
                        callback(true);
                    } else {
                        // не отправляем кэлбек если авторизация не удалась 
                        callback(false);
                    }
                });
        }
 
        function Logout() {
            // Удаляем пользователя из локального хранилища
            delete $localStorage.currentUser;
            $http.defaults.headers.common.Authorization = '';
        }
    }
})();
 

# Контроллер Home.Index

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

 
 
(function () {
    'use strict';
 
    angular
        .module('app')
        .controller('Home.IndexController', Controller);
 
    function Controller() {
        var vm = this;
 
        initController();
 
        function initController() {
        }
    }
})();
 

А так же Вью которую обрабатывает наш Контроллер:

 
 
<div class="col-md-6 col-md-offset-3>
    <h1>Home</h1>
    <p>You're logged in with JWT!!</p>
    <p><a href="#/login">Logout</a></p>
</div>

 

# Контроллер страницы Login.Index

Данный контроллер отвечает за Вью с Login.Index при входе на данную страницу срабатывает функция AuthenticationService.Logout() которая гарантирует, что пользователь выйдет из системы прежде чем попробует авторизоваться, а так же отправляет пользователя на главную страницу при авторизации связываясь с сервисом.

 
 

(function () {
    'use strict';
 
    angular
        .module('app')
        .controller('Login.IndexController', Controller);
 
    function Controller($location, AuthenticationService) {
        var vm = this;
 
        vm.login = login;
 
        initController();
 
        function initController() {
            // Выводим пользователя с сайта по средствам вызова данной функции
            AuthenticationService.Logout();
        };
 
        function login() {
            vm.loading = true; //Запускаем прелоадер между авторизации и переходом 
            AuthenticationService.Login(vm.username, vm.password, function (result) {
                if (result === true) {
                    $location.path('/'); // Если пользователь авторизовался перекидываем его на главную страницу 
                } else {
                    vm.error = 'Username or password is incorrect'; // Вывод ошибки при не удачной авторизации 
                    vm.loading = false; // отключаем прелоадер 
                }
            });
        };
    }
})();
 

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

 
 
<div class="col-md-6 col-md-offset-3">
    <div class="alert alert-info">
        Username: test<br />
        Password: test
    </div>
    <h2>Login</h2>
    <form name="form" ng-submit="form.$valid && vm.login()" novalidate>
        <div class="form-group" ng-class="{ 'has-error': form.$submitted && form.username.$invalid }">
            <label for="username">Username</label>
            <input type="text" name="username" class="form-control" ng-model="vm.username" required />
            <div ng-messages="form.$submitted && form.username.$error" class="help-block">
                <div ng-message="required">Username is required</div>
            </div>
        </div>
        <div class="form-group" ng-class="{ 'has-error': form.$submitted && form.password.$invalid }">
            <label for="password">Password</label>
            <input type="password" name="password" class="form-control" ng-model="vm.password" required />
            <div ng-messages="form.$submitted && form.password.$error" class="help-block">
                <div ng-message="required">Password is required</div>
            </div>
        </div>
        <div class="form-group">
            <button ng-disabled="vm.loading" class="btn btn-primary">Login</button>
            <img ng-if="vm.loading" src="data:image/gif;base64,R0lGODlhEAAQAPIAAP///wAAAMLCwkJCQgAAAGJiYoKCgpKSkiH/C05FVFNDQVBFMi4wAwEAAAAh/hpDcmVhdGVkIHdpdGggYWpheGxvYWQuaW5mbwAh QQJCgAAACwAAAAAEAAQAAADMwi63P4wyklrE2MIOggZnAdOmGYJRbExwroUmcG2LmDEwnHQLVsYOd2mBzkYDAdKa dIAAAh QQJCgAAACwAAAAAEAAQAAADNAi63P5OjCEgG4QMu7DmikRxQlFUYDEZIGBMRVsaqHwctXXf7WEYB4Ag1xjihkMZsiUkKhIAIfkECQoAAAAsAAAAABAAEAAAAzYIujIjK8pByJDMlFYvBoVjHA70GU7xSUJhmKtwHPAKzLO9HMaoKwJZ7Rf8AYPDDzKpZBqfvwQAIfkECQoAAAAsAAAAABAAEAAAAzMIumIlK8oyhpHsnFZfhYumCYUhDAQxRIdhHBGqRoKw0R8DYlJd8z0fMDgsGo/IpHI5TAAAIfkECQoAAAAsAAAAABAAEAAAAzIIunInK0rnZBTwGPNMgQwmdsNgXGJUlIWEuR5oWUIpz8pAEAMe6TwfwyYsGo/IpFKSAAAh QQJCgAAACwAAAAAEAAQAAADMwi6IMKQORfjdOe82p4wGccc4CEuQradylesojEMBgsUc2G7sDX3lQGBMLAJibufbSlKAAAh QQJCgAAACwAAAAAEAAQAAADMgi63P7wCRHZnFVdmgHu2nFwlWCI3WGc3TSWhUFGxTAUkGCbtgENBMJAEJsxgMLWzpEAACH5BAkKAAAALAAAAAAQABAAAAMyCLrc/jDKSatlQtScKdceCAjDII7HcQ4EMTCpyrCuUBjCYRgHVtqlAiB1YhiCnlsRkAAAOwAAAAAAAAAAAA==" />
        </div>
        <div ng-if="vm.error" class="alert alert-danger">{{vm.error}}</div>
    </form>
</div>


# Главный файл App.js

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

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

Функция config () используется для определения маршрутов приложения с использованием Angular UI Router, функция run () содержит логику запуска приложения, включая код, который проверяет локальное хранилище, чтобы пользователь мог свободно перемещаться между страницами и его не выкидывало при каждой перезагрузке и сеансами браузера, и добавляет Обработчик события события $locationChangeStart, который перенаправляет неавторизованных пользователей на страницу входа.

 
 

(function () {
    'use strict';
 
    angular
        .module('app', ['ui.router', 'ngMessages', 'ngStorage', 'ngMockE2E'])
        .config(config)
        .run(run);
 
    function config($stateProvider, $urlRouterProvider) {
        // стандартный роут
        $urlRouterProvider.otherwise("/");
 
        // Все основные роуты и какие контроллеры за них отвечают 
        $stateProvider
            .state('home', { // название роута 
                url: '/', // ссылка на страницу 
                templateUrl: 'home/index.view.html', // html страница представления 
                controller: 'Home.IndexController', // Его контроллер 
                controllerAs: 'vm' // this 
            })
            .state('login', {
                url: '/login',
                templateUrl: 'login/index.view.html',
                controller: 'Login.IndexController',
                controllerAs: 'vm'
            });
    }
 
    function run($rootScope, $http, $location, $localStorage) {
        // Оставляем пользователя авторизованным если страница перезагрузится 
        if ($localStorage.currentUser) {
            $http.defaults.headers.common.Authorization = 'Bearer ' + $localStorage.currentUser.token;
        }
 
        // Выкидываем пользователя на страницу авторизации если он не авторизован 
        $rootScope.$on('$locationChangeStart', function (event, next, current) {
            var publicPages = ['/login'];
            var restrictedPage = publicPages.indexOf($location.path()) === -1;
            if (restrictedPage && !$localStorage.currentUser) {
                $location.path('/login');
            }
        });
    }
})();

 

И наш основной Html файл который в котором мы подключаем все воедино:

 
<!DOCTYPE html>
<html ng-app="app">
<head>
    <title>AngularJS JWT Authentication Example & Tutorial</title>
 
    <!-- bootstrap css -->
    <link href="//netdna.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" rel="stylesheet" />
 
    <!-- application css -->
    <link href="app-content/app.css" rel="stylesheet" />
</head>
<body>
    <!-- main app container -->
    <div class="jumbotron">
        <div class="container">
            <div class="col-sm-8 col-sm-offset-2">
                <ui-view></ui-view>
            </div>
        </div>
    </div>
 
    <!-- credits -->
    <div class="text-center">
        <p>
            <a href="http://jasonwatmore.com/post/2016/04/05/AngularJS-JWT-Authentication-Example-Tutorial.aspx" target="_top">AngularJS JWT Authentication Example & Tutorial</a>
        </p>
        <p>
            <a href="http://jasonwatmore.com" target="_top">JasonWatmore.com</a>
        </p>
    </div>
 
    <!-- angular scripts -->
    <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.5.3/angular.min.js"></script>
    <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.5.3/angular-messages.min.js"></script>
    <script src="//cdnjs.cloudflare.com/ajax/libs/angular-ui-router/0.2.18/angular-ui-router.min.js"></script>
    <script src="//cdnjs.cloudflare.com/ajax/libs/ngStorage/0.3.6/ngStorage.min.js"></script>
 
    <!-- application scripts -->
    <script src="app.js"></script>
    <script src="app-services/authentication.service.js"></script>
    <script src="home/index.controller.js"></script>
    <script src="login/index.controller.js"></script>
 
    <!-- scripts for fake backend -->
    <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.5.3/angular-mocks.js"></script>
    <script src="app-helpers/fake-backend.js"></script>
</body>
</html>


 

Надеюсь данный урок был полезен, он может служить Вам как стандартный старт для любого проекта. Весь код я залью отдельно на данный репозиторий где с ним можно будет познакомиться поближе. Cпасибо все за внимание!

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *