Статьи

Аутентификация с Angular и Auth0

Эта статья об угловой аутентификации была первоначально опубликована в блоге Auth0.com и публикуется здесь с разрешения.

В этом уроке мы собираемся создать приложение Angular и добавить функцию входа в систему с использованием аутентификации на основе токенов с Auth0 .

Вы можете проверить законченный пример кода из нашего репозитория GitHub .

Угловая экосистема

AngularJS 1.x высоко ценился как надежная структура для создания одностраничных приложений (SPA). Он многое сделал хорошо, не справился с некоторыми, но в целом позволил разработчикам быстро создавать мощные приложения.

В то время как AngularJS (1.x) — это фреймворк, Angular — это целая платформа для создания современных приложений. Наряду с базовой библиотекой Angular платформа поставляется с мощным интерфейсом командной строки (CLI), называемым Angular CLI, который позволяет разработчикам легко создавать свои приложения и управлять системой сборки. Сервер Angular Platform обеспечивает рендеринг на стороне сервера для приложений Angular. Angular Material является официальной реализацией Google Material Design , которая позволяет разработчикам легко создавать красивые приложения.

Наше приложение: ежедневные предложения

Приложение ежедневных сделок

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

Обслуживание ежедневных сделок

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

'use strict'; // Load dependencies const express = require('express'); const app = express(); const cors = require('cors'); const bodyParser = require('body-parser'); app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: true })); app.use(cors()); // Public route app.get('/api/deals/public', (req, res)=>{ let deals = [ // Array of public deals here ]; res.json(deals); }) // Private route app.get('/api/deals/private', (req,res)=>{ let deals = [ // Array of Private Deals here ]; res.json(deals); }) app.listen(3001); console.log('Serving deals on localhost:3001'); 

Как нашему серверу, так и приложению Angular, которое мы создаем, потребуются Node.js и NPM , поэтому обязательно установите их перед продолжением. Проверьте репозиторий GitHub, чтобы получить наш список ежедневных предложений или создать свой собственный. Модель для каждой сделки будет выглядеть следующим образом:

  { id: 1234, name: 'Name of Product', description: 'Description of Product', originalPrice: 19.99, // Original price of product salePrice: 9.99 // Sale price of product } 

Когда вы довольны публичными и частными сделками, запустите сервер, запустив node server и перейдите к localhost:3001/api/deals/public и localhost:3001/api/deals/private чтобы убедиться, что вы видите список о сделках, которые вы добавили. Далее, давайте настроим наш угловой интерфейс.

Угловая настройка переднего конца

Один из лучших способов начать создание нового приложения Angular — использовать официальный Angular CLI. CLI может позаботиться о создании исходного приложения, добавлении дополнительных компонентов, заботе о системе сборки и многом другом. В этом уроке мы добавим наше первоначальное приложение в CLI.

Если он еще не установлен, запустите:

 npm install @angular/cli -g 

Это устанавливает Angular CLI глобально. Мы будем взаимодействовать с CLI с помощью команды ng . Чтобы создать новое приложение, выберите каталог и запустите:

 ng new ng2auth --routing --skip-tests 

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

Как только ng new закончится, введите новый каталог и выполните команду ng serve и система сборки на основе Webpack позаботится о компиляции нашего приложения из TypeScript в JavaScript и будет обслуживать наше приложение на localhost:4200 . Команда ng serve также запускает процесс прямой синхронизации, поэтому каждый раз, когда мы вносим изменения, наше приложение автоматически перекомпилируется.

Давайте сейчас localhost:4200 к localhost:4200 чтобы убедиться, что все работает так, как ожидалось. Если вы видите сообщение «приложение работает!», Вы — золотой. Далее давайте рассмотрим, как создается наше приложение Angular.

Команда ng new создала наше приложение Angular и добавила много файлов. Многие из них мы можем пока игнорировать, например, папку e2e , которая будет содержать наши e2e тесты. Откройте каталог src . В каталоге src мы можем видеть некоторые знакомые файлы, такие как index.html , styles.css и так далее. Откройте каталог app .

Каталог app содержит основную часть нашего приложения. По умолчанию нам представлены следующие файлы:

  • app.component.css — содержит стили CSS для нашего корневого компонента.
  • app.component.html — содержит представление HTML для нашего корневого компонента.
  • app.component.ts — содержит логику TypeScript для нашего корневого класса компонента
  • app.module.ts — определяет наши глобальные зависимости приложений
  • app-routing.module.ts — определяет маршруты нашего приложения.

Каждый компонент Angular, который мы пишем, будет иметь как минимум файл *.component.ts , остальные необязательны. Наше приложение будет состоять из трех компонентов. Основной или корневой компонент, компонент для отображения открытых сделок и компонент для отображения частных сделок. Для нашего корневого компонента мы встроим шаблон и стили. Давайте внесем следующие изменения и запустим следующие команды консоли:

  • Удалите app.component.css и app.component.html . Мы определим все, что нам нужно для нашего корневого компонента, в файле app.component.ts .
  • Создайте компонент public-deals , запустив ng gc public-deals --no-spec . Этот компонент позаботится о получении и отображении данных публичных сделок.
  • Создайте компонент private-deals , запустив ng gc private-deals --no-spec . Этот компонент позаботится о получении и отображении данных о частных сделках.
  • Создайте файл ng gc callback --it --is --flat --no-spec .
  • Создайте файл deal , запустив ng g class deal --no-spec . Этот файл будет содержать наш класс deal , который позволит Angular знать структуру deal .
  • Создайте файл deal.service.ts , выполнив deal.service.ts ng gs deal --no-spec . Здесь мы добавим функциональность для получения и получения данных о сделках из нашего API.

Примечание: g — это ярлык для generate , а c и s — это ярлыки для component и service соответственно. Следовательно, ng gc эквивалентно ng generate component . Флаг *.spec.ts --no-spec указывает, что файлы *.spec.ts не должны генерироваться. --it и --is обозначают «встроенный шаблон» и «встроенные стили», а --flat указывает, что содержащую папку не следует создавать.

Добавление модуля HTTP-клиента

Мы собираемся делать HTTP-запросы к нашему API в нашем приложении Angular. Для этого нам нужно добавить правильный модуль в наш файл app.module.ts . Давайте сделаем это сейчас, импортировав HttpClientModule и добавив его в массив import нашего @ NgModule следующим образом:

 // app.module.ts ... import { HttpClientModule } from '@angular/common/http'; @NgModule({ declarations: [ ... ], imports: [ ..., HttpClientModule ], providers: [], bootstrap: [AppComponent] }) export class AppModule { } 

Добавление Bootstrap CSS

Мы будем использовать Bootstrap для стилизации нашего приложения, поэтому давайте включим CSS в <head> нашего файла index.html следующим образом:

 <!-- src/index.html --> ... <link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet"> ... 

Создание корневого компонента

Каждое приложение Angular должно иметь корневой компонент. Мы можем назвать его как угодно, но важно то, что он у нас есть. В нашем приложении файл app.component.ts будет нашим корневым компонентом. Давайте посмотрим на нашу реализацию этого компонента.

 // app.component.ts import { Component } from '@angular/core'; @Component({ selector: 'app-root', template: ` <div class="container"> <nav class="navbar navbar-default"> <div class="navbar-header"> <a class="navbar-brand" routerLink="/dashboard">{{ title }}</a> </div> <ul class="nav navbar-nav"> <li> <a routerLink="/deals" routerLinkActive="active">Deals</a> </li> <li> <a routerLink="/special" routerLinkActive="active">Private Deals</a> </li> </ul> <ul class="nav navbar-nav navbar-right"> <li> <a>Log In</a> </li> <li> <a>Log Out</a> </li> </ul> </nav> <div class="col-sm-12"> <router-outlet></router-outlet> </div> </div> `, styles: [ `.navbar-right { margin-right: 0px !important}` ] }) export class AppComponent { title = 'Daily Deals'; constructor() {} } 

Мы создали наш корневой компонент. Мы добавили встроенный шаблон и несколько встроенных стилей. Мы еще не добавили все функции, поэтому каждый пользователь сможет видеть все ссылки, а также кнопки входа и выхода из системы. Мы будем ждать, чтобы реализовать их немного. Мы также отображаем элемент <router-outlet> . Здесь будут показаны наши перенаправленные компоненты.

Маршрутизация

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

Откройте файл app-routing.module.ts и добавьте следующее:

 // app-routing.module.ts import { NgModule } from '@angular/core'; import { Routes, RouterModule } from '@angular/router'; import { CallbackComponent } from './callback.component'; import { PublicDealsComponent } from './public-deals/public-deals.component'; import { PrivateDealsComponent } from './private-deals/private-deals.component'; const routes: Routes = [ { path: '', redirectTo: 'deals', pathMatch: 'full' }, { path: 'deals', component: PublicDealsComponent }, { path: 'special', component: PrivateDealsComponent }, { path: 'callback', component: CallbackComponent } ]; @NgModule({ imports: [RouterModule.forRoot(routes)], exports: [RouterModule] }) export class AppRoutingModule { } 

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

Тип сделки

TypeScript позволяет нам определять структуру или тип наших объектов. Это служит куче полезных целей. Например, если мы определим структуру объекта, мы сможем получить все данные объекта через IntelliSense. Мы можем дополнительно протестировать наши компоненты, зная структуру данных или тип объекта, с которым мы имеем дело.

Для нашего приложения мы создадим один такой тип. В файле deal.ts мы определим тип сделки. Посмотрим, как мы этого добьемся.

 // deal.ts export class Deal { id: number; name: string; description: string; originalPrice: number; salePrice: number; } 

Теперь мы можем объявить объекты в нашем приложении Angular типом deal . Эти объекты получат все свойства и методы типа сделки. Мы только определяем свойства здесь; у нас не будет никаких методов.

Компоненты государственных и частных сделок

Компоненты публичных и частных сделок очень похожи. Фактически, единственное различие между этими двумя реализациями состоит в том, что одна будет отображать предложения из открытого API, а другая будет отображать предложения из частного API. Для краткости мы просто покажем одну из реализаций компонента. Давайте реализуем public-deals.component.ts :

 // public-deals.component.ts import { Component, OnInit, OnDestroy } from '@angular/core'; import { Subscription } from 'rxjs/Subscription'; import { Deal } from '../deal'; // We haven't defined these services yet import { AuthService } from '../auth.service'; import { DealService } from '../deal.service'; @Component({ selector: 'app-public-deals', // We'll use an external file for both the CSS styles and HTML view templateUrl: 'public-deals.component.html', styleUrls: ['public-deals.component.css'] }) export class PublicDealsComponent implements OnInit, OnDestroy { dealsSub: Subscription; publicDeals: Deal[]; error: any; // Note: We haven't implemented the Deal or Auth Services yet. constructor( public dealService: DealService, public authService: AuthService) { } // When this component is loaded, we'll call the dealService and get our public deals. ngOnInit() { this.dealsSub = this.dealService .getPublicDeals() .subscribe( deals => this.publicDeals = deals, err => this.error = err ); } ngOnDestroy() { this.dealsSub.unsubscribe(); } } 

Мы будем использовать подписку RxJS, чтобы подписаться на наблюдаемую информацию, созданную нашим HTTP-запросом (который будет определен в Deal Service, который мы вскоре создадим), и предпримем некоторые действия, как только будет доступно значение для установки члена publicDeals , или определить error . Нам нужно добавить хук жизненного цикла OnDestroy с помощью ngOnDestroy() который отписывается при уничтожении компонента, чтобы предотвратить утечки памяти.

Далее, давайте построим представление о нашем компоненте публичных сделок. Мы сделаем это в файле public-deals.component.html . Наше мнение будет смесью HTML и Angular Sugar. Давайте посмотрим на нашу реализацию.

 <h3 class="text-center">Daily Deals</h3> <!-- We are going to get an array of deals stored in the publicDeals variable. We'll loop over that variable here using the ngFor directive --> <div class="col-sm-4" *ngFor="let deal of publicDeals"> <div class="panel panel-default"> <div class="panel-heading"> <h3 class="panel-title">{{ deal.name }}</h3> </div> <div class="panel-body"> {{ deal.description }} </div> <div class="panel-footer"> <ul class="list-inline"> <li>Original</li> <li class="pull-right">Sale</li> </ul> <ul class="list-inline"> <li><a class="btn btn-danger">${{ deal.originalPrice | number }}</a></li> <li class="pull-right"><a class="btn btn-success" (click)="dealService.purchase(deal)">${{ deal.salePrice | number }}</a></li> </ul> </div> </div> </div> <!-- We are going to use the authService.isLoggedIn method to see if the user is logged in or not. If they are not logged in we'll encourage them to login, otherwise if they are authenticated, we'll provide a handy link to private deals. We haven't implemented the authService yet, so don't worry about the functionality just yet --> <div class="col-sm-12" *ngIf="!authService.isLoggedIn"> <div class="jumbotron text-center"> <h2>Get More Deals By Logging In</h2> </div> </div> <div class="col-sm-12" *ngIf="authService.isLoggedIn"> <div class="jumbotron text-center"> <h2>View Private Deals</h2> <a class="btn btn-lg btn-success" routerLink="/special">Private Deals</a> </div> </div> <!-- If an error occurs, we'll show an error message --> <div class="col-sm-12 alert alert-danger" *ngIf="error"> <strong>Oops!</strong> An error occurred fetching data. Please try again. </div> 

Наконец, давайте добавим собственный стиль. В файле public-deals.component.css добавьте следующее:

 .panel-body { min-height: 100px; } 

Это обеспечит хорошее отображение каждого из продуктов на нашей странице.

Наш компонент частных сделок будет выглядеть очень похоже. Для краткости мы не будем показывать эшафот. Мы расскажем об изменениях чуть позже. Если вы хотите посмотреть, как это выглядит, вы можете посмотреть его в репозитории GitHub .

Доступ к нашему API сделок

Ранее в этом уроке мы написали очень простой API, который раскрыл два маршрута. Теперь давайте напишем сервис Angular, который будет взаимодействовать с этими двумя конечными точками. Мы сделаем это в файле deal.service.ts . Реализация заключается в следующем:

 // deal.service.ts import { Injectable } from '@angular/core'; import { HttpClient, HttpErrorResponse } from '@angular/common/http'; import { Observable } from 'rxjs/Observable'; import { catchError } from 'rxjs/operators'; import 'rxjs/add/observable/throw'; @Injectable() export class DealService { // Define the routes we are going to interact with private publicDealsUrl = 'http://localhost:3001/api/deals/public'; private privateDealsUrl = 'http://localhost:3001/api/deals/private'; constructor(private http: HttpClient) { } // Implement a method to get the public deals getPublicDeals() { return this.http .get(this.publicDealsUrl) .pipe( catchError(this.handleError) ); } // Implement a method to get the private deals getPrivateDeals() { return this.http .get(this.privateDealsUrl) .pipe( catchError(this.handleError) ); } // Implement a method to handle errors if any private handleError(err: HttpErrorResponse | any) { console.error('An error occurred', err); return Observable.throw(err.message || err); } // Create a shared method that shows an alert when someone buys a deal purchase(item) { alert(`You bought the: ${item.name}`); } } 

Теперь вы можете увидеть, куда getPublicDeals() метод getPublicDeals() из нашего файла public-deals.component.ts . Мы также написали метод getPrivateDeals() , который получит наш список частных сделок. private-deals.component.ts этот метод в своем файле private-deals.component.ts . Наконец, мы обрабатываем ошибки и реализуем метод purchase() который используется в обоих компонентах сделок.

Как только этот сервис создан, нам нужно импортировать его в наш файл app.module.ts и предоставить его следующим образом:

 // app.module.ts import { DealService } from './deal.service'; ... @NgModule({ ... providers: [ DealService ], ... 

Теперь сервис доступен для использования во всем нашем приложении.

Добавление аутентификации в ваше угловое приложение

Перейдите к localhost:4200 и вы увидите, что вы будете автоматически перенаправлены на страницу сделок. Обратите внимание, что вы можете свободно переходить к /special маршруту и ​​видеть эксклюзивные предложения. Вы можете сделать это, потому что мы еще не добавили аутентификацию пользователя. Давайте сделаем это сейчас.

Большинство приложений требуют определенного типа аутентификации. Наше приложение сегодня ничем не отличается. В следующем разделе я покажу вам, как правильно добавить аутентификацию в ваше приложение Angular. Мы собираемся использовать Auth0 в качестве нашей платформы идентификации. Мы будем использовать Auth0, поскольку он позволяет нам легко выпускать веб-токены JSON (JWT) , но концепции, которые мы рассмотрим, могут быть применены к любой системе аутентификации на основе токенов. Если у вас еще нет учетной записи Auth0, подпишитесь на бесплатную сейчас.

Отсюда, нажмите на пункт меню APIs и затем кнопку Create API . Вам нужно будет дать вашему API имя и идентификатор. Имя может быть любым по вашему выбору, поэтому сделайте его описательным, как вы хотите. Идентификатор будет использоваться для идентификации вашего API, и это поле нельзя изменить после его установки. В нашем примере я назову API API Daily Deals, а для идентификатора — http://localhost:3001 . Мы оставим алгоритм подписи как RS256 и нажмем кнопку « Создать API» .

Создание Auth0 API

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

Защита нашего сервера

Прежде чем мы осуществим аутентификацию на внешнем интерфейсе в нашем приложении Angular, давайте защитим наш внутренний сервер.

Сначала мы установим зависимости:

 npm install express-jwt jwks-rsa --save 

Откройте файл server.js расположенный в каталоге вашего server и внесите следующие изменения:

 // server.js 'use strict'; const express = require('express'); const app = express(); // Import the required dependencies const jwt = require('express-jwt'); const jwks = require('jwks-rsa'); const cors = require('cors'); const bodyParser = require('body-parser'); app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: true })); app.use(cors()); // We're going to implement a JWT middleware that will ensure the validity of our token. We'll require each protected route to have a valid access_token sent in the Authorization header const authCheck = jwt({ secret: jwks.expressJwtSecret({ cache: true, rateLimit: true, jwksRequestsPerMinute: 5, jwksUri: "https://{YOUR-AUTH0-DOMAIN}.auth0.com/.well-known/jwks.json" }), // This is the identifier we set when we created the API audience: '{YOUR-API-AUDIENCE-ATTRIBUTE}', issuer: "{YOUR-AUTH0-DOMAIN}", // eg, you.auth0.com algorithms: ['RS256'] }); app.get('/api/deals/public', (req, res)=>{ let deals = [ // Array of public deals ]; res.json(deals); }) // For the private route, we'll add this authCheck middleware app.get('/api/deals/private', authCheck, (req,res)=>{ let deals = [ // Array of private deals ]; res.json(deals); }) app.listen(3001); console.log('Listening on localhost:3001'); 

Это все, что нам нужно сделать на сервере. Перезагрузите сервер и попробуйте перейти к localhost:3001/api/deals/private и вы увидите сообщение об ошибке, в котором отсутствует заголовок авторизации. Наш частный маршрут API теперь защищен. Давайте перейдем к реализации аутентификации в нашем приложении Angular.

API без токена авторизации

Добавление аутентификации в интерфейс

Войдите в свою панель управления Auth0 и давайте внесем некоторые изменения в наш клиент , щелкнув элемент « Клиенты» на боковой панели. Найдите тестовый клиент, который был создан автоматически при создании нашего API. Это должно называться что-то вроде Daily Deals (Test Client) .

Измените тип клиента на Single Page Application . Затем добавьте http://localhost:4200/callback в поле « Разрешенные http://localhost:4200/callback .

Наконец, нажмите на ссылку Advanced Settings внизу и выберите вкладку OAuth . Убедитесь, что алгоритм подписи JsonWebToken установлен на RS256 .

Запишите идентификатор клиента ; это нам понадобится для настройки конфигурации аутентификации нашего приложения Angular.

Библиотека Auth0.js

Теперь нам нужно установить библиотеку auth0-js . Мы можем сделать это следующим образом в корневой папке нашего приложения Angular:

 npm install auth0-js --save 

Конфигурация среды Auth0

Откройте файл src/environments/environment.ts и добавьте свойство auth к константе со следующей информацией:

 // environment.ts export const environment = { production: false, auth: { clientID: 'YOUR-AUTH0-CLIENT-ID', domain: 'YOUR-AUTH0-DOMAIN', // eg, you.auth0.com audience: 'YOUR-AUTH0-API-IDENTIFIER', // eg, http://localhost:3001 redirect: 'http://localhost:4200/callback', scope: 'openid profile email' } }; 

Этот файл содержит переменные конфигурации аутентификации, поэтому мы можем использовать Auth0 для защиты нашего интерфейса. Обязательно обновите YOUR-AUTH0-CLIENT-ID , YOUR-AUTH0-DOMAIN и YOUR-AUTH0-API-IDENTIFIER до своей собственной информации из настроек клиента Auth0 и настроек API.

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

Далее мы создадим сервис аутентификации, который мы сможем использовать в нашем приложении:

 ng gs auth/auth --no-spec 

Это создаст новую папку в src/app/auth с файлом auth.service.ts внутри.

Откройте этот файл и измените его следующим образом:

 // auth.service.ts import { Injectable } from '@angular/core'; import * as auth0 from 'auth0-js'; import { environment } from './../../environments/environment'; import { Router } from '@angular/router'; @Injectable() export class AuthService { // Create Auth0 web auth instance auth0 = new auth0.WebAuth({ clientID: environment.auth.clientID, domain: environment.auth.domain, responseType: 'token', redirectUri: environment.auth.redirect, audience: environment.auth.audience, scope: environment.auth.scope }); // Store authentication data userProfile: any; accessToken: string; authenticated: boolean; constructor(private router: Router) { // Check session to restore login if not expired this.getAccessToken(); } login() { // Auth0 authorize request this.auth0.authorize(); } handleLoginCallback() { // When Auth0 hash parsed, get profile this.auth0.parseHash((err, authResult) => { if (authResult && authResult.accessToken) { window.location.hash = ''; this.getUserInfo(authResult); } else if (err) { console.error(`Error: ${err.error}`); } this.router.navigate(['/']); }); } getAccessToken() { this.auth0.checkSession({}, (err, authResult) => { if (authResult && authResult.accessToken) { this.getUserInfo(authResult); } else if (err) { console.log(err); this.logout(); this.authenticated = false; } }); } getUserInfo(authResult) { // Use access token to retrieve user's profile and set session this.auth0.client.userInfo(authResult.accessToken, (err, profile) => { if (profile) { this._setSession(authResult, profile); } }); } private _setSession(authResult, profile) { const expTime = authResult.expiresIn * 1000 + Date.now(); // Save authentication data and update login status subject localStorage.setItem('expires_at', JSON.stringify(expTime)); this.accessToken = authResult.accessToken; this.userProfile = profile; this.authenticated = true; } logout() { // Remove auth data and update login status localStorage.removeItem('expires_at'); this.userProfile = undefined; this.accessToken = undefined; this.authenticated = false; } get isLoggedIn(): boolean { // Check if current date is before token // expiration and user is signed in locally const expiresAt = JSON.parse(localStorage.getItem('expires_at')); return Date.now() < expiresAt && this.authenticated; } } 

После того как служба аутентификации была создана, нам нужно импортировать ее в наш файл app.module.ts и предоставить ее следующим образом:

 // app.module.ts import { AuthService } from './auth/auth.service'; ... @NgModule({ ... providers: [ ..., AuthService ], ... 

Теперь сервис доступен для использования во всем нашем приложении.

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

Угловая аутентификация All In

Маршрутизатор Angular поставляется с мощной функцией, называемой ограничителями маршрута, которая позволяет нам программно определять, может ли пользователь получить доступ к маршруту или нет. Например, защиту маршрутов в Angular можно сравнить с промежуточным ПО в Express.js.

Мы создадим средство проверки подлинности маршрута, которое проверит, вошел ли пользователь перед отображением маршрута. Создайте новую защиту, выполнив следующую команду CLI:

 ng g guard auth/auth --no-spec 

Откройте созданный файл auth.guard.ts и внесите следующие изменения:

 // auth.guard.ts import { Injectable } from '@angular/core'; import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router'; import { Observable } from 'rxjs/Observable'; import { AuthService } from './auth.service'; import { Router } from '@angular/router'; @Injectable() export class AuthGuard implements CanActivate { constructor( private authService: AuthService, private router: Router ) {} canActivate( next: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean { if (!this.authService.isLoggedIn) { this.router.navigate(['/']); return false; } return true; } } 

Чтобы реализовать эту app-routing.module.ts маршрута в наших маршрутах, давайте откроем наш файл app-routing.module.ts . Здесь мы включим нашу службу аутентификации и включим ее на нашем секретном маршруте. Давайте посмотрим на реализацию.

 // app-routing.module.ts ... // Import the AuthGuard import { AuthGuard } from './auth/auth.guard'; const routes: Routes = [ ..., { path: 'special', component: PrivateDealsComponent, // Add this to guard this route canActivate: [ AuthGuard ] }, ... ]; @NgModule({ ..., // Add AuthGuard to the providers array providers: [AuthGuard], ... }) export class AppRoutingModule { } 

Это все, что нужно сделать. Наш маршрут теперь защищен на уровне маршрутизации.

Если вы помните, мы включили заглушку для AuthService в наши компоненты сделки. Поскольку служба аутентификации теперь реализована, наша функциональность заполнителя будет работать. Мы увидим правильное поведение, отображаемое в зависимости от состояния пользователя.

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

 // app.component.ts import { Component } from '@angular/core'; import { AuthService } from './auth/auth.service'; @Component({ selector: 'app-root', template: ` <div class="container"> <nav class="navbar navbar-default"> <div class="navbar-header"> <a class="navbar-brand" routerLink="/">{{ title }}</a> </div> <ul class="nav navbar-nav"> <li> <a routerLink="/deals" routerLinkActive="active">Deals</a> </li> <li> <a routerLink="/special" *ngIf="authService.isLoggedIn" routerLinkActive="active">Private Deals</a> </li> </ul> <ul class="nav navbar-nav navbar-right"> <li> <a *ngIf="!authService.isLoggedIn" (click)="authService.login()">Log In</a> </li> <li> <a (click)="authService.logout()" *ngIf="authService.isLoggedIn">Log Out</a> </li> </ul> </nav> <div class="col-sm-12"> <router-outlet></router-outlet> </div> </div> `, styles: [ `.navbar-right { margin-right: 0px !important}` ] }) export class AppComponent { title = 'Daily Deals'; constructor(public authService: AuthService) {} } 

Мы импортировали AuthService и сделали его общедоступным в нашем конструкторе (он должен быть public чтобы шаблон мог использовать свои методы).

Мы добавили *ngIf="authService.isLoggedIn в нашу ссылку на частные сделки, чтобы она не отображалась, если пользователь не вошел в систему. Мы также добавили логику *ngIf в наши ссылки для входа и выхода, чтобы показать соответствующую ссылку в зависимости от пользователя состояние аутентификации. Когда пользователь нажимает на ссылку входа в систему сейчас, он будет перенаправлен на централизованную страницу входа в домене Auth0, где он введет здесь свои учетные данные и, если они верны, будет перенаправлен обратно в приложение.

Компонент обратного вызова

Теперь мы закодируем компонент обратного вызова, который мы сгенерировали в начале урока. Этот компонент будет активирован при вызове маршрута localhost:4200/callback , и он обработает перенаправление из Auth0 и обеспечит получение правильных данных обратно в хеш после успешной аутентификации. Для этого компонент будет использовать созданный ранее AuthService . Давайте посмотрим на реализацию:

 // callback.component.ts import { Component, OnInit } from '@angular/core'; import { AuthService } from './auth/auth.service'; @Component({ selector: 'app-callback', template: ` <p> Loading... </p> `, styles: [] }) export class CallbackComponent implements OnInit { constructor(private authService: AuthService) { } ngOnInit() { this.authService.handleLoginCallback(); } } 

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

Обновление Сервиса

Есть одно последнее обновление, которое нам нужно сделать. Если вы попытаетесь получить доступ к /special маршруту сейчас, даже если вы вошли в систему, вы не получите список секретных сделок. Это потому, что мы не передаем токен доступа на сервер. Нам придется обновить наш сервис сделки.

Нам нужно обновить вызов /api/deals/private чтобы включить наш токен доступа. Нам нужно импортировать HttpHeaders, чтобы прикрепить к нашему запросу заголовок authorization со схемой носителя. Нам также нужно будет импортировать наш AuthService чтобы получить доступ к accessToken . Давайте посмотрим, как мы собираемся реализовать это в нашем приложении.

 // deal.service.ts ... // Import HttpHeaders import { HttpClient, HttpHeaders, HttpErrorResponse } from '@angular/common/http'; // Import AuthService import { AuthService } from './auth/auth.service'; ... constructor( private http: HttpClient, private authService: AuthService ) { } ... // Implement a method to get the private deals getPrivateDeals() { return this.http .get(this.privateDealsUrl, { headers: new HttpHeaders().set('Authorization', `Bearer ${this.authService.accessToken}`) }) .pipe( catchError(this.handleError) ); } 

Мы добавим заголовок Authorization в наш getPrivateDeals() используя токен из службы аутентификации. Теперь, когда вызов сделан на частный маршрут в нашем API, мы автоматически authService.accessToken к вызову. Давайте попробуем это в следующем разделе, чтобы убедиться, что это работает.

Собираем все вместе

Auth0 централизованный вход

Вот и все. Теперь мы готовы протестировать наше приложение. Если ваш сервер Node.js не работает, сначала запустите его. Перейдите на localhost:4200 и вы автоматически будете перенаправлены на localhost:4200/deals и посмотрите список публичных сделок.

Аутентифицированные ежедневные предложения

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

Согласие Диалог

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

Эксклюзивные ежедневные предложения

Вы только что написали и аутентифицировали приложение Angular. Congrats!

Вывод

В этом руководстве мы рассмотрели некоторые способы написания компонентов и сервисов Angular. Мы реализовали аутентификацию на основе токенов с помощью Auth0. Но это только царапина на поверхности.

Angular предоставляет множество полезных функций, таких как pipe, i18n и многое другое. Auth0 может помочь защитить ваши приложения Angular не только с помощью современной аутентификации, но и с такими расширенными функциями, как многофакторная аутентификация , обнаружение аномалий , корпоративная федерация , единый вход (SSO) и многое другое. Зарегистрируйтесь сегодня, чтобы вы могли сосредоточиться на создании функций, уникальных для вашего приложения.