Статьи

Создайте угловое приложение с аутентификацией за 20 минут

Эта статья была первоначально опубликована в блоге разработчиков OKTA . Спасибо за поддержку партнеров, которые делают возможным использование SitePoint.

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

Angular CLI позволяет легко создавать новые компоненты и даже целые проекты. Если вы не использовали Angular CLI для быстрой генерации Angular-кода, вас ждет угощение!

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

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

СОВЕТ: Если вы хотите пропустить создание приложения Angular и получить право на добавление аутентификации, вы можете клонировать мой проект ng-demo , а затем перейти к разделу Создание приложения OpenID Connect в Okta .

 git clone https://github.com/mraible/ng-demo.git 

Что вам нужно

  • Около 20 минут
  • Любимый текстовый редактор или IDE. Я рекомендую IntelliJ IDEA
  • Node.js и npm установлены. Я рекомендую использовать nvm
  • Угловой CLI установлен. Если у вас не установлен Angular CLI, установите его с помощью npm install -g @angular/cli

Создайте новый проект с помощью команды ng new :

 ng new ng-demo 

Это создаст ng-demo проект ng-demo и запустит в нем npm install . Это может занять около минуты, но это может варьироваться в зависимости от скорости вашего соединения.

 [mraible:~/dev] $ ng new ng-demo installing ng create .editorconfig create README.md create src/app/app.component.css create src/app/app.component.html create src/app/app.component.spec.ts create src/app/app.component.ts create src/app/app.module.ts create src/assets/.gitkeep create src/environments/environment.prod.ts create src/environments/environment.ts create src/favicon.ico create src/index.html create src/main.ts create src/polyfills.ts create src/styles.css create src/test.ts create src/tsconfig.app.json create src/tsconfig.spec.json create src/typings.d.ts create .angular-cli.json create e2e/app.e2e-spec.ts create e2e/app.po.ts create e2e/tsconfig.e2e.json create .gitignore create karma.conf.js create package.json create protractor.conf.js create tsconfig.json create tslint.json Successfully initialized git. Installing packages for tooling via npm. Installed packages for tooling via npm. You can `ng set --global packageManager=yarn`. Project 'ng-demo' successfully created. [mraible:~] 46s $ 

Вы можете увидеть, какую версию Angular CLI вы используете с помощью ng --version .

 $ ng --version _ _ ____ _ ___ / \ _ __ __ _ _ _| | __ _ _ __ / ___| | |_ _| / △ \ | '_ \ / _` | | | | |/ _` | '__| | | | | | | / ___ \| | | | (_| | |_| | | (_| | | | |___| |___ | | /_/ \_\_| |_|\__, |\__,_|_|\__,_|_| \____|_____|___| |___/ @angular/cli: 1.3.2 node: 8.4.0 os: darwin x64 

Запустите ваше угловое приложение

Проект настроен с помощью веб-пакета dev server . Чтобы начать, убедитесь, что вы находитесь в каталоге ng-demo , затем запустите:

 ng serve 

Вы должны увидеть экран, подобный приведенному ниже, по адресу http: // localhost: 4200 .

Домашняя страница по умолчанию

Вы можете убедиться, что тесты вашего нового проекта пройдены, запустите ng test :

 $ ng test ... Chrome 60.0.3112 (Mac OS X 10.12.6): Executed 3 of 3 SUCCESS (0.239 secs / 0.213 secs) 

Добавить функцию поиска

Чтобы добавить функцию поиска, откройте проект в IDE или в своем любимом текстовом редакторе. Для IntelliJ IDEA используйте Файл> Новый проект> Статическая сеть и укажите каталог ng-demo .

В окне терминала перейдите в каталог вашего проекта и выполните следующую команду. Это создаст поисковый компонент.

 $ ng g component search installing component create src/app/search/search.component.css create src/app/search/search.component.html create src/app/search/search.component.spec.ts create src/app/search/search.component.ts update src/app/app.module.ts 

Откройте src/app/search/search.component.html и замените его HTML- src/app/search/search.component.html умолчанию следующим:

 <h2>Search</h2> <form> <input type="search" name="query" [(ngModel)]="query" (keyup.enter)="search()"> <button type="button" (click)="search()">Search</button> </form> <pre>{{searchResults | json}}</pre> 

Документация по Router для Angular предоставляет информацию, необходимую для настройки маршрута к только что сгенерированному SearchComponent . Вот краткое резюме:

В src/app/app.module.ts добавьте константу appRoutes и импортируйте ее в @NgModule :

 import { Routes, RouterModule } from '@angular/router'; const appRoutes: Routes = [ {path: 'search', component: SearchComponent}, {path: '', redirectTo: '/search', pathMatch: 'full'} ]; @NgModule({ ... imports: [ ... RouterModule.forRoot(appRoutes) ] ... }) export class AppModule { } 

В src/app/app.component.html настройте содержимое заполнителя и добавьте <router-outlet> для отображения маршрутов.

 <h1>Welcome to {{title}}!</h1> <!-- Routed views go here --> <router-outlet></router-outlet> 

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

Если вы по-прежнему работаете, ваш браузер должен автоматически обновиться. Если нет, перейдите по адресу http: // localhost: 4200. Скорее всего, вы увидите пустой экран. Откройте консоль JavaScript, и вы увидите проблему.

Ошибка модели NG

Чтобы решить эту проблему, откройте src/app/app.module.ts и добавьте FormsModule в качестве импорта в @NgModule :

 import { FormsModule } from '@angular/forms'; @NgModule({ ... imports: [ ... FormsModule ] ... }) export class AppModule { } 

Теперь вы должны увидеть форму поиска.

Поиск без CSS

Если вы хотите добавить CSS для этих компонентов, откройте src/app/search/search.component.css и добавьте немного CSS. Например:

 :host { display: block; padding: 0 20px; } 

В этом разделе показано, как создать новый компонент для базового приложения Angular с помощью Angular CLI. В следующем разделе будет показано, как создать и использовать файл JSON и localStorage для создания поддельного API.

Чтобы получить результаты поиска, создайте SearchService который отправляет HTTP-запросы в файл JSON. Начните с создания нового сервиса.

 $ ng g service search installing service create src/app/search.service.spec.ts create src/app/search.service.ts WARNING Service is generated but not provided, it must be provided to be used 

Переместите сгенерированный search.service.ts и его тест в app/shared/search . Вам нужно будет создать этот каталог.

 mkdir -p src/app/shared/search mv src/app/search.service.* src/app/shared/search/. 

Создайте src/assets/data/people.json для хранения ваших данных.

 [ { "id": 1, "name": "Peyton Manning", "phone": "(303) 567-8910", "address": { "street": "1234 Main Street", "city": "Greenwood Village", "state": "CO", "zip": "80111" } }, { "id": 2, "name": "Demaryius Thomas", "phone": "(720) 213-9876", "address": { "street": "5555 Marion Street", "city": "Denver", "state": "CO", "zip": "80202" } }, { "id": 3, "name": "Von Miller", "phone": "(917) 323-2333", "address": { "street": "14 Mountain Way", "city": "Vail", "state": "CO", "zip": "81657" } } ] 

Измените src/app/shared/search/search.service.ts и предоставьте Http в качестве зависимости в своем конструкторе. В этом же файле создайте метод getAll() чтобы собрать всех людей. Кроме того, определите классы Address и Person которые будет перенаправляться JSON.

 import { Injectable } from '@angular/core'; import { Http, Response } from '@angular/http'; import 'rxjs/add/operator/map'; @Injectable() export class SearchService { constructor(private http: Http) {} getAll() { return this.http.get('assets/data/people.json') .map((res: Response) => res.json()); } } export class Address { street: string; city: string; state: string; zip: string; constructor(obj?: any) { this.street = obj && obj.street || null; this.city = obj && obj.city || null; this.state = obj && obj.state || null; this.zip = obj && obj.zip || null; } } export class Person { id: number; name: string; phone: string; address: Address; constructor(obj?: any) { this.id = obj && Number(obj.id) || null; this.name = obj && obj.name || null; this.phone = obj && obj.phone || null; this.address = obj && obj.address || null; } } 

Чтобы сделать эти классы доступными для использования вашими компонентами, отредактируйте src/app/shared/index.ts и добавьте следующее:

 export * from './search/search.service'; 

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

В src/app/search/search.component.ts добавьте импорт для этих классов.

 import { Person, SearchService } from '../shared'; 

Теперь вы можете добавить переменные query и searchResults . Пока вы там, измените конструктор, чтобы добавить SearchService .

 export class SearchComponent implements OnInit { query: string; searchResults: Array<Person>; constructor(private searchService: SearchService) {} 

Затем реализуйте метод search() для вызова метода getAll() службы.

 search(): void { this.searchService.getAll().subscribe( data => { this.searchResults = data; }, error => console.log(error) ); } 

В этот момент вы, скорее всего, увидите следующее сообщение в консоли вашего браузера.

 ORIGINAL EXCEPTION: No provider for SearchService! 

Чтобы исправить ошибку «Нет поставщика», обновите app.module.ts чтобы импортировать SearchService и добавить службу в список поставщиков. Поскольку SearchService зависит от Http , вам также необходимо импортировать HttpModule .

 import { SearchService } from './shared'; import { HttpModule } from '@angular/http'; @NgModule({ ... imports: [ ... HttpModule ], providers: [SearchService], bootstrap: [AppComponent] }) 

Теперь нажатие кнопки поиска должно работать. Чтобы результаты выглядели лучше, удалите <pre> и замените его на <table> в src/app/search/search.component.html .

 <table *ngIf="searchResults"> <thead> <tr> <th>Name</th> <th>Phone</th> <th>Address</th> </tr> </thead> <tbody> <tr *ngFor="let person of searchResults; let i=index"> <td>{{person.name}}</td> <td>{{person.phone}}</td> <td>{{person.address.street}}<br/> {{person.address.city}}, {{person.address.state}} {{person.address.zip}} </td> </tr> </tbody> </table> 

Затем добавьте несколько дополнительных CSS в src/app/search/search.component.css чтобы улучшить макет таблицы.

 table { margin-top: 10px; border-collapse: collapse; } th { text-align: left; border-bottom: 2px solid #ddd; padding: 8px; } td { border-top: 1px solid #ddd; padding: 8px; } 

Теперь результаты поиска выглядят лучше.

результаты поиска

Но подождите, у вас все еще нет функции поиска! Чтобы добавить функцию поиска, добавьте метод search() в SearchService .

 import { Observable } from 'rxjs'; search(q: string): Observable<any> { if (!q || q === '*') { q = ''; } else { q = q.toLowerCase(); } return this.getAll().map(data => data.filter(item => JSON.stringify(item).toLowerCase().includes(q))); } 

Затем рефакторинг SearchComponent для вызова этого метода с его переменной query .

 search(): void { this.searchService.search(this.query).subscribe( data => { this.searchResults = data; }, error => console.log(error) ); } 

Теперь результаты поиска будут отфильтрованы по введенному вами значению запроса.

В этом разделе показано, как получить и отобразить результаты поиска. Следующий раздел основан на этом и показывает, как редактировать и сохранять записи.

Добавить функцию редактирования

Измените src/app/search/search.component.html чтобы добавить ссылку для редактирования человека.

 <td><a [routerLink]="['/edit', person.id]">{{person.name}}</a></td> 

Выполните следующую команду, чтобы сгенерировать EditComponent .

 $ ng g component edit installing component create src/app/edit/edit.component.css create src/app/edit/edit.component.html create src/app/edit/edit.component.spec.ts create src/app/edit/edit.component.ts update src/app/app.module.ts 

Добавьте маршрут для этого компонента в src/app/app.module.ts :

 const appRoutes: Routes = [ {path: 'search', component: SearchComponent}, {path: 'edit/:id', component: EditComponent}, {path: '', redirectTo: '/search', pathMatch: 'full'} ]; 

Обновите src/app/edit/edit.component.html для отображения редактируемой формы. Вы можете заметить, что я добавил атрибуты id для большинства элементов. Это должно упростить процесс написания интеграционных тестов с помощью Protractor.

 <div *ngIf="person"> <h3>{{editName}}</h3> <div> <label>Id:</label> {{person.id}} </div> <div> <label>Name:</label> <input [(ngModel)]="editName" name="name" id="name" placeholder="name"/> </div> <div> <label>Phone:</label> <input [(ngModel)]="editPhone" name="phone" id="phone" placeholder="Phone"/> </div> <fieldset> <legend>Address:</legend> <address> <input [(ngModel)]="editAddress.street" id="street"><br/> <input [(ngModel)]="editAddress.city" id="city">, <input [(ngModel)]="editAddress.state" id="state" size="2"> <input [(ngModel)]="editAddress.zip" id="zip" size="5"> </address> </fieldset> <button (click)="save()" id="save">Save</button> <button (click)="cancel()" id="cancel">Cancel</button> </div> 

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

 import { Component, OnInit, OnDestroy } from '@angular/core'; import { Address, Person, SearchService } from '../shared'; import { Subscription } from 'rxjs'; import { ActivatedRoute, Router } from '@angular/router'; @Component({ selector: 'app-edit', templateUrl: './edit.component.html', styleUrls: ['./edit.component.css'] }) export class EditComponent implements OnInit, OnDestroy { person: Person; editName: string; editPhone: string; editAddress: Address; sub: Subscription; constructor(private route: ActivatedRoute, private router: Router, private service: SearchService) { } ngOnInit() { this.sub = this.route.params.subscribe(params => { const id = + params['id']; // (+) converts string 'id' to a number this.service.get(id).subscribe(person => { if (person) { this.editName = person.name; this.editPhone = person.phone; this.editAddress = person.address; this.person = person; } else { this.gotoList(); } }); }); } ngOnDestroy() { this.sub.unsubscribe(); } cancel() { this.router.navigate(['/search']); } save() { this.person.name = this.editName; this.person.phone = this.editPhone; this.person.address = this.editAddress; this.service.save(this.person); this.gotoList(); } gotoList() { if (this.person) { this.router.navigate(['/search', {term: this.person.name} ]); } else { this.router.navigate(['/search']); } } } 

Измените SearchService чтобы он содержал функции для поиска человека по его идентификатору и сохранения его. Пока вы там, измените метод search() чтобы быть в курсе обновленных объектов в localStorage .

 search(q: string): Observable<any> { if (!q || q === '*') { q = ''; } else { q = q.toLowerCase(); } return this.getAll().map(data => { const results: any = []; data.map(item => { // check for item in localStorage if (localStorage['person' + item.id]) { item = JSON.parse(localStorage['person' + item.id]); } if (JSON.stringify(item).toLowerCase().includes(q)) { results.push(item); } }); return results; }); } get(id: number) { return this.getAll().map(all => { if (localStorage['person' + id]) { return JSON.parse(localStorage['person' + id]); } return all.find(e => e.id === id); }); } save(person: Person) { localStorage['person' + person.id] = JSON.stringify(person); } 

Вы можете добавить CSS в src/app/edit/edit.component.css если хотите, чтобы форма выглядела немного лучше.

 :host { display: block; padding: 0 20px; } button { margin-top: 10px; } 

На этом этапе вы сможете найти человека и обновить его информацию.

Редактировать форму

<form> src/app/edit/edit.component.html <form> в src/app/edit/edit.component.html вызывает функцию save() для обновления данных человека. Вы уже реализовали это выше.
Функция вызывает функцию gotoList() которая добавляет имя человека в URL при отправке пользователя обратно на экран поиска.

 gotoList() { if (this.person) { this.router.navigate(['/search', {term: this.person.name} ]); } else { this.router.navigate(['/search']); } } 

Поскольку SearchComponent не выполняет поиск автоматически при выполнении этого URL, добавьте следующую логику, чтобы сделать это в своем конструкторе.

 import { ActivatedRoute } from '@angular/router'; import { Subscription } from 'rxjs'; ... sub: Subscription; constructor(private searchService: SearchService, private route: ActivatedRoute) { this.sub = this.route.params.subscribe(params => { if (params['term']) { this.query = decodeURIComponent(params['term']); this.search(); } }); } 

Вы захотите реализовать OnDestroy и определить метод ngOnDestroy для очистки этой подписки.

 import { Component, OnInit, OnDestroy } from '@angular/core'; export class SearchComponent implements OnInit, OnDestroy { ... ngOnDestroy() { this.sub.unsubscribe(); } } 

После внесения всех этих изменений вы сможете искать / редактировать / обновлять информацию о человеке. Если это работает — хорошая работа!

Проверка формы

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

Чтобы сделать имя обязательным, измените edit.component.html чтобы добавить required атрибут к имени <input> .

 <input [(ngModel)]="editName" name="name" id="name" placeholder="name" required/> 

Вам также нужно будет обернуть все в элемент <form> . Добавьте <form> после <h3> и закройте его до последнего </div> . Вам также необходимо добавить обработчик (ngSubmit) в форму и изменить кнопку сохранения на обычную кнопку отправки.

 <h3>{{editName}}</h3> <form (ngSubmit)="save()" ngNativeValidate> ... <button type="submit" id="save">Save</button> <button (click)="cancel()" id="cancel">Cancel</button> </form> 

После внесения этих изменений любое поле с required атрибутом будет обязательным.

Изменить форму проверки

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

 If ngModel is used within a form tag, either the name attribute must be set or the form control must be defined as 'standalone' in ngModelOptions. Example 1: <input [(ngModel)]="person.firstName" name="first"> Example 2: <input [(ngModel)]="person.firstName" [ngModelOptions]="{standalone: true}"> 

Чтобы исправить, добавьте атрибут name ко всем адресным полям. Например:

 <address> <input [(ngModel)]="editAddress.street" name="street" id="street"><br/> <input [(ngModel)]="editAddress.city" name="city" id="city">, <input [(ngModel)]="editAddress.state" name="state" id="state" size="2"> <input [(ngModel)]="editAddress.zip" name="zip" id="zip" size="5"> </address> 

Теперь значения должны отображаться во всех полях и name должно быть обязательным.

Изменить имена форм

Если вы хотите предоставить свои собственные сообщения проверки, а не полагаться на браузер, выполните следующие шаги:

  1. Удалите ngNativeValidate и добавьте #editForm="ngForm" к элементу <form> .
  2. Добавьте #name="ngModel" к #name="ngModel" <input id="name"> .
  3. Добавьте [disabled]="!editForm.form.valid" к кнопке Сохранить .
  4. Добавьте следующее в поле name чтобы отобразить ошибку проверки.
 <div [hidden]="name.valid || name.pristine" style="color: red"> Name is required </div> 

Чтобы узнать больше о формах и проверке, смотрите документацию угловых форм .

Создать приложение OpenID Connect в Okta

OpenID Connect (OIDC) построен на основе протокола OAuth 2.0. Это позволяет клиентам проверять личность пользователя, а также получать информацию об их базовом профиле. Чтобы узнать больше, см. Https://openid.net/connect .

Чтобы интегрировать Okta для аутентификации пользователя, вам сначала нужно зарегистрироваться и создать приложение OIDC.

Войдите в свою учетную запись Okta или создайте ее, если у вас ее еще нет. Перейдите в Приложения и нажмите кнопку Добавить приложение . Выберите SPA и нажмите Далее . На следующей странице укажите http://localhost:4200 в качестве базового URI, URI перенаправления входа в систему и URI перенаправления выхода из системы. Нажмите Готово, и вы должны увидеть настройки, подобные следующим.

Настройки приложения OIDC

Установите проект Manfred Steyer, чтобы добавить поддержку OAuth 2 и OpenID Connect, используя npm.

 npm install --save angular-oauth2-oidc 

Измените src/app/app.component.ts чтобы импортировать OAuthService и настройте ваше приложение на использование параметров приложения Okta.

 import { OAuthService, JwksValidationHandler } from 'angular-oauth2-oidc'; ... constructor(private oauthService: OAuthService) { this.oauthService.redirectUri = window.location.origin; this.oauthService.clientId = '{client-id}'; this.oauthService.scope = 'openid profile email'; this.oauthService.issuer = 'https://dev-{dev-id}.oktapreview.com'; this.oauthService.tokenValidationHandler = new JwksValidationHandler(); // Load Discovery Document and then try to login the user this.oauthService.loadDiscoveryDocument().then(() => { this.oauthService.tryLogin(); }); } ... 

Создайте src/app/home/home.component.ts и настройте его так, чтобы в нем были кнопки входа и src/app/home/home.component.ts

 import { Component } from '@angular/core'; import { OAuthService } from 'angular-oauth2-oidc'; @Component({ template: ` <div *ngIf="givenName"> <h2>Welcome, {{givenName}}!</h2> <button (click)="logout()">Logout</button> <p><a routerLink="/search" routerLinkActive="active">Search</a></p> </div> <div *ngIf="!givenName"> <button (click)="login()">Login</button> </div>` }) export class HomeComponent { constructor(private oauthService: OAuthService) { } login() { this.oauthService.initImplicitFlow(); } logout() { this.oauthService.logOut(); } get givenName() { const claims = this.oauthService.getIdentityClaims(); if (!claims) { return null; } return claims['name']; } } 

Создайте src/app/shared/auth/auth.guard.service.ts чтобы перейти к HomeComponent если пользователь не аутентифицирован.

 import { Injectable } from '@angular/core'; import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from '@angular/router'; import { OAuthService } from 'angular-oauth2-oidc'; @Injectable() export class AuthGuard implements CanActivate { constructor(private oauthService: OAuthService, private router: Router) {} canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean { if (this.oauthService.hasValidIdToken()) { return true; } this.router.navigate(['/home']); return false; } } 

Экспортируйте AuthGuard в src/shared/index.ts :

 export * from './auth/auth.guard.service'; 

Импортируйте OAuthModule в src/app/app.module.ts , настройте новый HomeComponent и заблокируйте маршруты /search и /edit с помощью AuthGuard .

 import { OAuthModule } from 'angular-oauth2-oidc'; import { HomeComponent } from './home/home.component'; import { SearchService, AuthGuard } from './shared'; const appRoutes: Routes = [ {path: 'search', component: SearchComponent, canActivate: [AuthGuard]}, {path: 'edit/:id', component: EditComponent, canActivate: [AuthGuard]}, {path: 'home', component: HomeComponent}, {path: '', redirectTo: 'home', pathMatch: 'full'}, {path: '**', redirectTo: 'home'} ]; @NgModule({ declarations: [ ... HomeComponent ], imports: [ ... OAuthModule.forRoot() ], providers: [ AuthGuard, SearchService ], bootstrap: [AppComponent] }) export class AppModule { } 

После внесения этих изменений вы сможете запустить ng serve и увидеть кнопку входа.

Кнопка входа в Okta

Нажмите кнопку « Вход» и войдите в систему с помощью одного из людей, настроенных в вашем приложении Okta.

Форма входа Okta

После входа вы сможете нажать « Поиск» и просмотреть информацию о людях.

Okta Post Войти

Если это работает — отлично! Если вы хотите создать собственную форму входа в свое приложение, продолжайте чтение, чтобы узнать, как использовать Okta Auth SDK с OAuthService .

Аутентификация с помощью Okta Auth SDK

Пакет Okta Auth SDK построен на основе API аутентификации Otka и API OAuth 2.0, что позволяет вам создавать полноценные возможности входа с использованием JavaScript.

Установите его, используя npm:

 npm install @okta/okta-auth-js --save 

Добавьте ссылку на основной файл JavaScript этой библиотеки в .angular-cli.json :

 "scripts": [ "../node_modules/@okta/okta-auth-js/dist/okta-auth-js.min.js" ], 

Компоненты в этом разделе используют классы Bootstrap CSS. Установите Bootstrap 4.

 npm install [email protected] --save 

Измените src/styles.css чтобы добавить ссылку на CSS-файл Bootstrap.

 @import "~bootstrap/dist/css/bootstrap.css"; 

Обновите src/app/app.component.html чтобы использовать классы Bootstrap для своей панели навигации и грид-системы.

 <nav class="navbar navbar-light bg-secondary"> <a class="navbar-brand text-light" href="#">Welcome to {{title}}!</a> </nav> <div class="container-fluid"> <router-outlet></router-outlet> </div> 

Создайте src/app/shared/auth/okta.auth.wrapper.ts чтобы обернуть Okta Auth SDK и интегрировать его с OAuthService . Его метод login() использует OktaAuth для получения токена сеанса и обмена его на ID и токены доступа.

 import { OAuthService } from 'angular-oauth2-oidc'; import { Injectable } from '@angular/core'; declare const OktaAuth: any; @Injectable() export class OktaAuthWrapper { private authClient: any; constructor(private oauthService: OAuthService) { this.authClient = new OktaAuth({ url: this.oauthService.issuer }); } login(username: string, password: string): Promise<any> { return this.oauthService.createAndSaveNonce().then(nonce => { return this.authClient.signIn({ username: username, password: password }).then((response) => { if (response.status === 'SUCCESS') { return this.authClient.token.getWithoutPrompt({ clientId: this.oauthService.clientId, responseType: ['id_token', 'token'], scopes: ['openid', 'profile', 'email'], sessionToken: response.sessionToken, nonce: nonce, redirectUri: window.location.origin }) .then((tokens) => { const idToken = tokens[0].idToken; const accessToken = tokens[1].accessToken; const keyValuePair = `#id_token=${encodeURIComponent(idToken)}&access_token=${encodeURIComponent(accessToken)}`; return this.oauthService.tryLogin({ <1> customHashFragment: keyValuePair, disableOAuth2StateCheck: true }); }); } else { return Promise.reject('We cannot handle the ' + response.status + ' status'); } }); }); } } 

В приведенном выше коде oauthService.tryLogin() анализирует и сохраняет idToken и idToken чтобы их можно было извлечь с помощью OAuthService.getIdToken() и OAuthService.getAccessToken() .

Экспорт OktaAuthWrapper в src/shared/index.ts :

 export * from './auth/okta.auth.wrapper'; 

Добавьте OktaAuthWrapper в качестве поставщика в app.module.ts .

 import { SearchService, AuthGuard, OktaAuthWrapper } from './shared'; @NgModule({ ... providers: [ ... OktaAuthWrapper ], bootstrap: [AppComponent] }) 

Измените HomeComponent для объявления OktaAuth и измените его template чтобы он имел кнопку для входа в систему, а также форму для входа.

 @Component({ template: ` <div *ngIf="givenName" class="col-12 mt-2"> <button (click)="logout()" class="btn btn-sm btn-outline-primary float-right">Logout</button> <h2>Welcome, {{givenName}}!</h2> <p><a routerLink="/search" routerLinkActive="active">Search</a></p> </div> <div class="card mt-2" *ngIf="!givenName"> <div class="card-body"> <h4 class="card-title">Login with Authorization Server</h4> <button class="btn btn-primary" (click)="login()">Login</button> </div> </div> <div class="card mt-2" *ngIf="!givenName"> <div class="card-body"> <h4 class="card-title">Login with Username/Password</h4> <p class="alert alert-error" *ngIf="loginFailed"> Login wasn't successful. </p> <div class="form-group"> <label>Username</label> <input class="form-control" [(ngModel)]="username"> </div> <div class="form-group"> <label>Password</label> <input class="form-control" type="password" [(ngModel)]="password"> </div> <div class="form-group"> <button class="btn btn-primary" (click)="loginWithPassword()">Login</button> </div> </div> </div>` }) 

После внесения этих изменений HomeComponent должен отобразиться следующим образом.

Пользовательская форма входа

Добавьте локальные переменные для полей имени пользователя и пароля, импортируйте OktaAuthWrapper и loginWithPassword() метод loginWithPassword() в HomeComponent .

 import { OktaAuthWrapper } from '../shared'; ... username; password; constructor(private oauthService: OAuthService, private oktaAuthWrapper: OktaAuthWrapper) { } loginWithPassword() { this.oktaAuthWrapper.login(this.username, this.password) .then(_ => console.debug('logged in')) .catch(err => console.error('error logging in', err)); } 

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

Вид после входа

Angular + Okta

Если все работает — поздравляю! Если у вас возникли проблемы, пожалуйста, опубликуйте вопрос в Stack Overflow с тегом okta или напишите мне на Twitter @mraible .

Вы можете найти законченную версию приложения, созданного в этой записи блога на GitHub . Чтобы узнать больше о безопасности в Angular, см . Документацию по безопасности Angular . Если вы хотите узнать больше об OpenID Connect, я рекомендую посмотреть успокаивающее видео ниже.