Эта статья была обновлена 11.05.2017, чтобы отразить важные изменения в API Auth0.
Аутентификация для одностраничных приложений может быть непростой задачей. Во многих случаях архитектура SPA предполагает наличие изолированного клиентского приложения с каркасом, подобным AngularJS, и отдельного серверного интерфейса, который служит API данных для подачи клиентскому интерфейсу. В этих случаях традиционная проверка подлинности на основе сеанса, которая выполняется в большинстве двусторонних приложений, не выполняется. Аутентификация на основе сеансов имеет много проблем для такого рода архитектуры, но, вероятно, самая большая из них заключается в том, что она вводит состояние в API, и один из принципов REST состоит в том, что все остается без состояния . Другое соображение заключается в том, что если вы когда-нибудь захотите использовать тот же API данных в качестве серверной части для мобильного приложения, аутентификация на основе сеансов не будет работать.
Веб-токены JSON
Чтобы обойти эти ограничения, мы можем использовать JSON Web Tokens (JWT) для добавления аутентификации в наши одностраничные приложения. JWT является открытым стандартом и предоставляет нам способ аутентификации запросов от нашего внешнего приложения AngularJS до нашего внутреннего API. JWT — это больше, чем просто токен. Одним из самых больших преимуществ JWT является то, что они включают полезную нагрузку данных, которая может содержать произвольные данные JSON в форме определяемых нами утверждений . Поскольку JWT имеют цифровую подпись с секретом, который находится на сервере, мы можем быть уверены, что они не могут быть подделаны и данные в полезной нагрузке не могут быть изменены до достижения бэкэнда.
Аутентификация для угловых приложений
JWT — это идеальное решение для добавления аутентификации в наши приложения AngularJS. Все, что нам нужно сделать для доступа к защищенным конечным точкам из нашего API, это сохранить JWT пользователя в локальном хранилище и затем отправить его в качестве заголовка Authorization
при выполнении HTTP-запросов. Если пользователь имеет недопустимый JWT или вообще не имеет JWT, его запрос на доступ к защищенным ресурсам будет отклонен, и он получит ошибку.
К сожалению, это был бы просто минимум для обработки аутентификации в приложениях AngularJS. Если мы вообще заботимся о пользовательском опыте, нам нужно сделать еще несколько вещей, чтобы наши приложения работали так, как и следовало ожидать. Мы должны:
- Условно показать или скрыть определенные элементы в зависимости от того, имеет ли пользователь действительный JWT (например, кнопки « Вход» и « Выход» )
- Защита определенных маршрутов, к которым не прошедший проверку пользователь не сможет получить доступ
- Обновите пользовательский интерфейс, когда состояние пользователя изменяется, если истекает срок действия их JWT или когда они выходят из системы
В этой статье мы реализуем аутентификацию от начала до конца в приложении AngularJS и даже создадим небольшой сервер NodeJS, чтобы посмотреть, как создавать запросы к защищенному ресурсу. Существует множество деталей относительно настройки базы данных пользователей и выпуска JWT, поэтому вместо того, чтобы делать это самостоятельно, мы будем использовать Auth0 (компанию, в которой я работаю), чтобы сделать это для нас. Auth0 предоставляет бесплатный план для 7000 активных пользователей, что дает нам достаточно места для многих реальных приложений. Мы также увидим, как мы можем легко добавить окно входа и даже использовать социальную аутентификацию с Auth0.
Прежде чем мы начнем, если вы хотите освежить в AngularJS, ознакомьтесь с разделом Создание приложения с AngularJS на SitePoint Premium.
Чтобы увидеть весь код этого урока, посмотрите репозиторий .
Подпишитесь на Auth0
Первое, что вам нужно для этого урока, — это учетная запись Auth0. При регистрации учетной записи вам нужно будет присвоить приложению доменное имя, которое нельзя изменить позже. Поскольку вы можете иметь несколько приложений под одной учетной записью, то, как вы будете называть свой домен, будет зависеть от вашей ситуации. В большинстве случаев лучше называть это именем, относящимся к вашей организации, например названием вашей компании. Если это имеет смысл, вы также можете использовать имя вашего приложения — это ваше дело. Ваш домен Auth0 принимает шаблон your-domain.auth0.com
и используется при настройке инструментов Auth0, которые мы увидим ниже.
После того, как вы зарегистрируетесь, вам будет предложено выбрать тип аутентификации для вашего приложения. Хорошо оставить настройки по умолчанию, так как вы сможете изменить их позже.
После того, как вы зарегистрировались, зайдите на свою панель, чтобы проверить вещи. Если вы нажмете ссылку « Клиенты» на левой боковой панели, вы увидите, что ваша учетная запись создается с помощью приложения по умолчанию . Нажмите «Приложение по умолчанию», чтобы увидеть свои учетные данные и другую информацию.
Сразу же мы должны заполнить наши URL-адреса Allowed Origins и Allowed Callback . Это поле используется для указания Auth0, каким доменам разрешено делать запросы для аутентификации пользователей, а также к каким доменам мы можем перенаправлять после аутентификации. В этом руководстве мы будем использовать http-sever , источник по умолчанию которого http://localhost:8080
.
Далее, поскольку мы создаем одностраничное приложение, которое будет взаимодействовать с бэкэндом API, давайте также создадим клиент API. Нажмите на ссылку API в главном меню. Отсюда нажмите кнопку « Создать API» , и вы увидите диалоговое окно, в котором вас попросят ввести некоторую информацию о вашем API. Все, что вам нужно предоставить, это имя и идентификатор . Запишите идентификатор, так как это значение будет использоваться в качестве идентификатора вашей аудитории для API. Оставьте алгоритм подписи как RS256 .
Благодаря бесплатному плану Auth0 мы можем использовать двух провайдеров социальных сетей, таких как Google, Twitter, Facebook и многие другие. Все, что нам нужно сделать, чтобы заставить их работать, это щелкнуть выключателем, и это можно сделать с помощью ссылки Соединения > Социальные сети на панели инструментов .
Установите зависимости и настройте Auth0
Для этого приложения нам понадобится несколько пакетов, некоторые из которых предоставляются Auth0 в качестве модулей с открытым исходным кодом. Если вы разветвили репозиторий GitHub , вы можете просто запустить bower install
чтобы установить все необходимые зависимости. После того, как зависимости установлены, вы захотите установить модуль http-server
глобально. Для этого введите следующую команду:
# To serve the app (if not already installed) npm install -g http-server
Наконец, чтобы запустить приложение, просто выполните команду http-server
из своего терминала или интерфейса командной строки.
Далее, давайте app.js
наши app.js
и index.html
для начальной загрузки приложения. В настоящее время мы можем сообщить Angular, к каким модулям нам нужен доступ из установленных нами зависимостей.
// app.js (function () { 'use strict'; angular .module('app', ['auth0.auth0', 'angular-jwt', 'ui.router']) .config(config); config.$inject = ['$stateProvider', '$locationProvider', 'angularAuth0Provider', '$urlRouterProvider', 'jwtOptionsProvider']; function config($stateProvider, $locationProvider, angularAuth0Provider, $urlRouterProvider, jwtOptionsProvider) { $stateProvider .state('home', { url: '/home', controller: 'HomeController', templateUrl: 'components/home/home.html', controllerAs: 'vm' }) // Initialization for the angular-auth0 library angularAuth0Provider.init({ clientID: AUTH0_CLIENT_ID, // Your Default Client ID domain: AUTH0_DOMAIN, // Your Auth0 Domain responseType: 'token id_token', redirectUri: AUTH0_CALLBACK_URL, // Your Callback URL audience: AUTH0_API_AUDIENCE, // The API Identifier value you gave your API }); // Configure a tokenGetter so that the isAuthenticated // method from angular-jwt can be used jwtOptionsProvider.config({ tokenGetter: function() { return localStorage.getItem('id_token'); } }); $urlRouterProvider.otherwise('/home'); // Remove the ! from the hash so that // auth0.js can properly parse it $locationProvider.hashPrefix(''); } })();
Здесь мы настроили authProvider
из auth0-angular с нашими учетными данными из панели мониторинга. Конечно, вы захотите заменить значения в образце своими учетными данными. Давайте также создадим файл app.run.js
и вставим следующий код:
// app.run.js (function () { 'use strict'; angular .module('app') .run(function ($rootScope, authService) { // Put the authService on $rootScope so its methods // can be accessed from the nav bar $rootScope.auth = authService; // Process the auth token if it exists and fetch the profile authService.handleParseHash(); }); })();
Эта часть функциональности будет выполнять синтаксический анализ хеш-функции для извлечения access_token
и id_token
возвращенных с обратным вызовом после успешной аутентификации пользователя. В реальном приложении у вас может быть определенный маршрут для обработки этого, такой как /callback
но для нашей простой демонстрационной программы этот запуск будет выполняться каждый раз, когда приложение обновляется.
access_token
будет отправлен вашему бэкэнд-API, и этот токен будет проверен для обеспечения правильного доступа. id_token
с другой стороны, предназначен для клиентского интерфейса и содержит пользовательские данные для клиента.
<!-- index.html --> <html> <head> <title>AngularJS Authentication</title> <!-- Viewport settings--> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" /> <!-- Basic style --> <link href="bower_components/bootstrap/dist/css/bootstrap.css" rel="stylesheet" /> <style> .navbar{ margin-bottom: 0; border-radius: 0; } </style> </head> <body> <div ng-app="app"> <nav class="navbar navbar-default"> <div class="container-fluid"> <div class="navbar-header"> <a class="navbar-brand" ui-sref="home">Auth0</a> </div> <div id="navbar" class="navbar-collapse collapse"> <ul class="nav navbar-nav"> <li ng-if="!auth.isAuthenticated()"><a ng-click="auth.login()">Log In</a></li> <li ng-if="auth.isAuthenticated()"><a ng-click="auth.logout()">Log Out</a></li> </ul> </div> </div> </nav> <div ui-view></div> </div> <script type="text/javascript" src="auth0-variables.js"></script> <script type="text/javascript" src="bower_components/angular/angular.js"></script> <script type="text/javascript" src="bower_components/angular-ui-router/release/angular-ui-router.js"></script> <script type="text/javascript" src="bower_components/auth0.js/build/auth0.js"></script> <script type="text/javascript" src="bower_components/angular-auth0/dist/angular-auth0.js"></script> <script type="text/javascript" src="bower_components/angular-jwt/dist/angular-jwt.js"></script> <script type="text/javascript" src="app.js"></script> <script type="text/javascript" src="app.run.js"></script> <script type="text/javascript" src="components/home/home.controller.js"></script> <script type="text/javascript" src="components/auth/auth.service.js"></script> </body> </html>
Теперь у нас есть основа для настройки нашего приложения. У нас есть простая панель инструментов в верхней части страницы, которая позволит пользователю войти в систему. В нижней части вы увидите группу импорта, которую мы еще не создали. Мы начнем строить их в следующем разделе.
Создать домашнюю страницу
В приложении есть несколько мест, в которые мы могли бы поместить элементы управления для аутентификации. Мы могли бы использовать sidenav, navbar, модал или даже смесь этих трех. Для простоты мы уже поместили кнопку входа в систему на панели инструментов, но для удобства пользователей добавим ее в наш основной вид. Если мы посмотрим на наш файл app.js
то увидим, что наш домашний компонент будет app.js
каталоге components/home
, поэтому создайте этот каталог рядом с файлом home.html
файлом home.html
для пользовательского интерфейса. Наш пользовательский интерфейс будет выглядеть так:
<!-- home.html --> <div class="jumbotron"> <h2 class="text-center"><img src="https://cdn.auth0.com/styleguide/1.0.0/img/badge.svg"></h2> <h2 class="text-center">Home</h2> <div class="text-center" ng-if="!vm.auth.isAuthenticated()"> <p>You are not yet authenticated. <a href="javascript:;" ng-click="vm.auth.login()">Log in.</a></p> </div> <div class="text-center" ng-if="vm.auth.isAuthenticated()"> <p>Thank you for logging in! <a href="javascript:;" ng-click="vm.auth.logout()">Log out.</a></p> </div> <div class="text-center"> <a ng-click="vm.getMessage()">Get Message</a> <span style="padding: 0 50px;">|</span> <a ng-click="vm.getSecretMessage()">Get Secret Message</a> <br /> <p>{{vm.message}}</p> </div> </div>
Для нашего файла home.controller.js
у нас будет следующий код:
// home.controller.js (function () { 'use strict'; angular .module('app') .controller('HomeController', homeController); homeController.$inject = ['authService', '$http']; function homeController(authService, $http) { var vm = this; vm.auth = authService; vm.getMessage = function() { $http.get('http://localhost:3001/api/public').then(function(response) { vm.message = response.data.message; }); } // Makes a call to a private endpoint. // We will append our access_token to the call and the backend will // verify that it is valid before sending a response. vm.getSecretMessage = function() { $http.get('http://localhost:3001/api/private', {headers : { Authorization: 'Bearer ' + localStorage.getItem('access_token') }}).then(function(response) { vm.message = response.data.message; }).catch(function(error){ vm.message = "You must be logged in to access this resource." }); } } })();
С нашего домашнего контроллера мы будем звонить в наш сервис API. У нас будет два вызова API, один для общедоступного маршрута API, доступ к которому может получить каждый, и один для защищенного маршрута, который может быть успешно доступен только зарегистрированному пользователю. Это нормально, если часть этого кода еще не имеет смысла. Мы углубимся в следующий раздел, когда создадим наш сервис аутентификации.
Создание службы аутентификации
До сих пор мы обращались к сервису аутентификации несколько раз, но фактически не создавали его. Давайте позаботимся об этом дальше. Служба аутентификации будет отвечать за вход в систему пользователей, управление состоянием аутентификации и так далее. Создайте новый каталог с именем auth
и там файл auth.service.js
. Наш сервис аутентификации будет выглядеть так:
// auth.service.js (function () { 'use strict'; angular .module('app') .service('authService', authService); authService.$inject = ['$state', 'angularAuth0', 'authManager']; function authService($state, angularAuth0, authManager) { // When a user calls the login function they will be redirected // to Auth0's hosted Lock and will provide their authentication // details. function login() { angularAuth0.authorize(); } // Once a user is successfuly authenticated and redirected back // to the AngularJS application we will parse the hash to extract // the idToken and accessToken for the user. function handleParseHash() { angularAuth0.parseHash( { _idTokenVerification: false }, function(err, authResult) { if (err) { console.log(err); } if (authResult && authResult.idToken) { setUser(authResult); } }); } // This function will destroy the access_token and id_token // thus logging the user out. function logout() { localStorage.removeItem('access_token'); localStorage.removeItem('id_token'); } // If we can successfuly parse the id_token and access_token // we wil store them in localStorage thus logging the user in function setUser(authResult) { localStorage.setItem('access_token', authResult.accessToken); localStorage.setItem('id_token', authResult.idToken); } // This method will check to see if the user is logged in by // checking to see whether they have an id_token stored in localStorage function isAuthenticated() { return authManager.isAuthenticated(); } return { login: login, handleParseHash: handleParseHash, logout: logout, isAuthenticated: isAuthenticated } } })();
Служба аутентификации довольно проста. У нас есть функции для обработки входа и выхода из приложения, а также проверки того, вошел ли пользователь в систему. Теперь наше приложение должно работать. Давайте пойдем дальше и посетим localhost: 8080, чтобы увидеть наше приложение в действии.
Если все прошло хорошо, вы должны увидеть загрузку приложения Angular и вы будете в состоянии выхода из системы.
Две ссылки внизу не будут работать в данный момент, так как мы еще не развернули сервер. Мы сделаем это в ближайшее время, но чтобы убедиться, что наше приложение работает, давайте попробуем войти в систему. Нажмите либо ссылку для входа в систему на наборе, либо в основном содержимом страницы, и вы будете перенаправлены на страницу входа в домен Auth0.
Здесь вы можете войти через любое настроенное вами соединение или даже зарегистрировать новую учетную запись. Войдите в систему, как хотите, и вы будете перенаправлены обратно в приложение AngularJS по адресу localhost:8080
но на этот раз вы будете в состоянии входа в систему.
Отлично. Для нашей последней части демонстрации давайте напишем простой Node-сервер для обработки наших вызовов API.
Создайте сервер NodeJS
Давайте теперь быстро настроим сервер NodeJS, чтобы мы могли делать запросы! Создайте новый каталог с именем server
а затем установите некоторые зависимости.
mkdir server && cd server npm init npm install express express-jwt cors jkws-rsa
После установки создайте приложение express , которое использует промежуточное ПО express-jwt . Вам понадобится информация об API Auth0. Поскольку мы уже создали API ранее, зайдите на свою панель инструментов , найдите API и скопируйте его ценность для аудитории. Посмотрите на реализацию ниже:
// server/server.js var express = require('express'); var app = express(); var jwt = require('express-jwt'); var jwks = require('jwks-rsa'); var cors = require('cors'); app.use(cors()); var authCheck = jwt({ secret: jwks.expressJwtSecret({ cache: true, rateLimit: true, jwksRequestsPerMinute: 5, jwksUri: "https://{YOUR-AUTH0-DOMAIN}.auth0.com/.well-known/jwks.json" }), audience: '{YOUR-AUTH0-API-AUDIENCE}', // Paste your API audience here. issuer: "https://{YOUR-AUTH0-DOMAIN}.auth0.com/", algorithms: ['RS256'] }); app.get('/api/public', function(req, res) { res.json({ message: "Hello from a public endpoint! You don't need to be authenticated to see this." }); }); // For the private call we are using the authCheck middleware to validate the token app.get('/api/private', authCheck, function(req, res) { res.json({ message: "Hello from a private endpoint! You DO need to be authenticated to see this." }); }); app.listen(3001); console.log('Listening on http://localhost:3001');
Промежуточное программное обеспечение express-jwt используется для защиты конечных точек от доступа, если только не отправлен действительный JWT. Затем нам просто нужно применить промежуточное программное обеспечение к любым маршрутам, которые мы хотим защитить, передав его в качестве второго аргумента, как мы это сделали для private
маршрута здесь.
Выполнение запросов API
Запустите сервер в новом окне / вкладке консоли с помощью командного node server.js
Теперь, если мы дойдем до нашего приложения AngularJS и нажмем кнопку « Получить сообщение» , мы увидим сообщение «Привет из общедоступной конечной точки…». Затем нажмите кнопку « Получить секретное сообщение» , и вы должны увидеть сообщение «Привет из частной конечной точки…». Это потому, что мы вошли в систему ранее, и вы все еще находитесь в состоянии входа в систему.
Давайте посмотрим, что происходит, когда вы не вошли в систему и попытаетесь получить доступ к секретному сообщению. Нажмите кнопку « Выйти» на панели навигации или в главном содержимом. Когда вы выйдете из системы, нажмите кнопку « Получить секретное сообщение» , и на этот раз вам будет представлено другое сообщение о том, что вы должны пройти аутентификацию, прежде чем сможете получить доступ к конечной точке.
Подробнее об Auth0
Auth0 также позволяет нам легко добавлять другие современные функции аутентификации в наши приложения, включая единый вход в систему , пароль без пароля и многофакторную аутентификацию .
Мы также не ограничены использованием NodeJS в качестве нашего бэкэнда. Доступны SDK для многих других, в том числе:
Для разработки мобильных приложений также доступны SDK:
Завершение
Добавление аутентификации в API и отправка аутентифицированных запросов к нему из приложения AngularJS относительно простое, но для обеспечения правильного взаимодействия с пользователем достаточно много шагов. Auth0 делает тяжелую работу для нас, когда дело доходит до аутентификации, потому что нам не нужно беспокоиться о сохранении нашей собственной базы данных пользователей, и при этом нам не нужно вставлять свой собственный логин.
Исходя из моего опыта, гораздо проще реализовать аутентификацию в приложении Angular 2, поскольку нам нужно меньше беспокоиться. Если вы заинтересованы в Angular 2, вы можете проверить этот пример о том, как создать работающее приложение с аутентификацией, а также взглянуть на документацию Auth0 .