Учебник. Вход пользователей в систему и вызов API Microsoft Graph из одностраничного приложения (SPA) Angular с помощью потока кода проверки подлинности

В этом руководстве вы создадите одностраничное приложение Angular, которое входит в систему пользователей и вызывает API Microsoft Graph с помощью потока кода авторизации с PKCE. Создаваемое одностраничное приложение использует библиотеку проверки подлинности Майкрософт (MSAL) для Angular версии 2.

В этом руководстве рассматриваются следующие темы:

  • Регистрация приложения в Центре администрирования Microsoft Entra
  • создание проекта Angular с помощью npm;
  • добавление кода для поддержки входа и выхода пользователей;
  • добавление кода для вызова API Microsoft Graph;
  • Тестирование приложения

MSAL Angular версии 2 — это расширенная версия MSAL Angular версии 1, которая поддерживает поток кода авторизации в браузере вместо потока неявного предоставления разрешения. MSAL Angular версии 2 НЕ поддерживает неявный поток.

Необходимые компоненты

  • Node.js для запуска локального веб-сервера.
  • Visual Studio Code или другой редактор для изменения файлов проекта.

Как работает пример приложения

Diagram showing the authorization code flow in a single-page application

Пример приложения, созданный по инструкциям из этого руководства, позволяет одностраничному приложению Angular выполнять запрос к API Microsoft Graph или веб-API, принимающему маркеры, выпущенные платформой удостоверений Майкрософт. В нем используется библиотека проверки подлинности Майкрософт (MSAL) для Angular версии 2, программа-оболочка для библиотеки MSAL.js версии 2. MSAL Angular позволяет приложениям Angular 9+ проверять подлинность корпоративных пользователей с помощью идентификатора Microsoft Entra, а также пользователей с учетными записями Майкрософт и социальными удостоверениями, такими как Facebook, Google и LinkedIn. Библиотека также позволяет приложениям получать доступ к облачным службам Майкрософт или Microsoft Graph.

В этом сценарии после входа пользователя в систему маркер доступа запрашивается и добавляется в HTTP-запросы с использованием заголовка авторизации. Получение и обновление маркера выполняются MSAL.

Библиотеки

В этом учебнике используются следующие библиотеки:

Библиотека Description
MSAL Angular Библиотека проверки подлинности Майкрософт для программы-оболочки JavaScript Angular
Браузер MSAL Библиотека проверки подлинности Майкрософт для пакета JavaScript версии 2 для браузера

Исходный код для всех библиотек MSAL.js можно найти в microsoft-authentication-library-for-js репозитории на сайте GitHub.

Получение готового примера кода

Вместо этого вы предпочитаете скачать готовый пример проекта для этого руководства? Клонирование ms-identity-javascript-angular-spa

git clone https://github.com/Azure-Samples/ms-identity-javascript-angular-spa.git

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

Регистрация идентификаторов приложения и записей

Совет

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

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

  1. Войдите в Центр администрирования Microsoft Entra как минимум разработчик приложений.
  2. Если у вас есть доступ к нескольким клиентам, используйте значок Параметры в верхнем меню, чтобы переключиться на клиент, в котором вы хотите зарегистрировать приложение из меню каталогов и подписок.
  3. Перейдите к приложениям> удостоверений>Регистрация приложений.
  4. Выберите Создать регистрацию.
  5. Введите имя приложения, например Angular-SPA-auth-code.
  6. Для параметра Поддерживаемые типы учетных записей выберите Учетные записи только в этом каталоге организации. Для получения сведений о различных типах учетных записей выберите параметр "Справка ".
  7. В разделе URI перенаправления (необязательно) используйте раскрывающееся меню, чтобы выбрать одностраничные приложения (SPA) и ввести http://localhost:4200 в текстовое поле.
  8. Выберите Зарегистрировать.
  9. Панель обзора приложения отображается при завершении регистрации. Запишите идентификатор каталога (клиента) и идентификатор приложения (клиента), которые будут использоваться в исходном коде приложения.

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

  1. Откройте Visual Studio Code, выберите "Открыть папку">.... Перейдите к папке и выберите расположение, в котором нужно создать проект.

  2. Откройте новый терминал, выбрав терминал>"Новый терминал".

    1. Может потребоваться переключить типы терминалов. Щелкните стрелку вниз рядом с значком + в терминале и выберите командную строку.
  3. Выполните следующие команды, чтобы создать новый проект Angular с именем msal-angular-tutorial, установить библиотеки компонентов Angular Material, MSAL Browser, MSAL Angular и создать компоненты домашнего и профиля.

    npm install -g @angular/cli
    ng new msal-angular-tutorial --routing=true --style=css --strict=false
    cd msal-angular-tutorial
    npm install @angular/material @angular/cdk
    npm install @azure/msal-browser @azure/msal-angular
    ng generate component home
    ng generate component profile
    

Настройка приложения и изменение базового пользовательского интерфейса

  1. Откройте src/app/app.module.ts. MsalInterceptor Необходимо MsalModule добавить imports константу вместе с константойisIE. Вы также добавите модули материалов. Замените все содержимое файла следующим фрагментом кода:

    import { BrowserModule } from "@angular/platform-browser";
    import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
    import { NgModule } from "@angular/core";
    
    import { MatButtonModule } from "@angular/material/button";
    import { MatToolbarModule } from "@angular/material/toolbar";
    import { MatListModule } from "@angular/material/list";
    
    import { AppRoutingModule } from "./app-routing.module";
    import { AppComponent } from "./app.component";
    import { HomeComponent } from "./home/home.component";
    import { ProfileComponent } from "./profile/profile.component";
    
    import { MsalModule, MsalRedirectComponent } from "@azure/msal-angular";
    import { PublicClientApplication } from "@azure/msal-browser";
    
    const isIE =
      window.navigator.userAgent.indexOf("MSIE ") > -1 ||
      window.navigator.userAgent.indexOf("Trident/") > -1;
    
    @NgModule({
      declarations: [AppComponent, HomeComponent, ProfileComponent],
      imports: [
        BrowserModule,
        BrowserAnimationsModule,
        AppRoutingModule,
        MatButtonModule,
        MatToolbarModule,
        MatListModule,
        MsalModule.forRoot(
          new PublicClientApplication({
            auth: {
              clientId: "Enter_the_Application_Id_here", // Application (client) ID from the app registration
              authority:
                "Enter_the_Cloud_Instance_Id_Here/Enter_the_Tenant_Info_Here", // The Azure cloud instance and the app's sign-in audience (tenant ID, common, organizations, or consumers)
              redirectUri: "Enter_the_Redirect_Uri_Here", // This is your redirect URI
            },
            cache: {
              cacheLocation: "localStorage",
              storeAuthStateInCookie: isIE, // Set to true for Internet Explorer 11
            },
          }),
          null,
          null
        ),
      ],
      providers: [],
      bootstrap: [AppComponent, MsalRedirectComponent],
    })
    export class AppModule {}
    
  2. Замените следующие значения значениями, полученными из Центра администрирования Microsoft Entra. Подробные сведения о доступных настраиваемых параметрах см. в статье Initialize client applications using MSAL.js (Инициализация клиентских приложений с помощью MSAL.js).

    • clientId — идентификатор приложения, который также называется клиентом. Замените Enter_the_Application_Id_Here значение идентификатора приложения (клиента), записанное ранее на странице обзора зарегистрированного приложения.
    • authority — Это состоит из двух частей:
      • Экземпляр — конечная точка поставщика облачных служб. Для основного или глобального облака Azure введите https://login.microsoftonline.com. Ознакомьтесь с различными доступными конечными точками в национальных облаках.
      • Идентификатор клиента — это идентификатор клиента, в котором зарегистрировано приложение. Замените _Enter_the_Tenant_Info_Here значение идентификатора каталога (клиента), записанное ранее на странице обзора зарегистрированного приложения.
    • redirectUri — расположение, в котором сервер авторизации отправляет пользователя после успешного разрешения приложения и предоставления кода авторизации или маркера доступа. Замените Enter_the_Redirect_Uri_Here на http://localhost:4200.
  3. Откройте src/app/app-routing.module.ts и добавьте маршруты в компоненты домашнегои профиля. Замените все содержимое файла следующим фрагментом кода:

    import { NgModule } from "@angular/core";
    import { Routes, RouterModule } from "@angular/router";
    import { BrowserUtils } from "@azure/msal-browser";
    import { HomeComponent } from "./home/home.component";
    import { ProfileComponent } from "./profile/profile.component";
    
    const routes: Routes = [
      {
        path: "profile",
        component: ProfileComponent,
      },
      {
        path: "",
        component: HomeComponent,
      },
    ];
    
    const isIframe = window !== window.parent && !window.opener;
    
    @NgModule({
      imports: [
        RouterModule.forRoot(routes, {
          // Don't perform initial navigation in iframes or popups
          initialNavigation:
            !BrowserUtils.isInIframe() && !BrowserUtils.isInPopup()
              ? "enabledNonBlocking"
              : "disabled", // Set to enabledBlocking to use Angular Universal
        }),
      ],
      exports: [RouterModule],
    })
    export class AppRoutingModule {}
    
  4. Откройте src/app/app.component.html и замените существующий код следующим кодом:

    <mat-toolbar color="primary">
      <a class="title" href="/">{{ title }}</a>
    
      <div class="toolbar-spacer"></div>
    
      <a mat-button [routerLink]="['profile']">Profile</a>
    
      <button mat-raised-button *ngIf="!loginDisplay" (click)="login()">Login</button>
    
    </mat-toolbar>
    <div class="container">
      <!--This is to avoid reload during acquireTokenSilent() because of hidden iframe -->
      <router-outlet *ngIf="!isIframe"></router-outlet>
    </div>
    
  5. Откройте src/style.css , чтобы определить CSS:

    @import "~@angular/material/prebuilt-themes/deeppurple-amber.css";
    
    html,
    body {
      height: 100%;
    }
    body {
      margin: 0;
      font-family: Roboto, "Helvetica Neue", sans-serif;
    }
    .container {
      margin: 1%;
    }
    
  6. Откройте src/app/app.component.css , чтобы добавить стили CSS в приложение:

    .toolbar-spacer {
      flex: 1 1 auto;
    }
    
    a.title {
      color: white;
    }
    

Вход с помощью всплывающих окон

  1. Откройте src/app/app.component.ts и замените содержимое файла следующим образом, чтобы войти пользователя с помощью всплывающего окна:

    import { MsalService } from '@azure/msal-angular';
    import { Component, OnInit } from '@angular/core';
    
    @Component({
      selector: 'app-root',
      templateUrl: './app.component.html',
      styleUrls: ['./app.component.css']
    })
    export class AppComponent implements OnInit {
      title = 'msal-angular-tutorial';
      isIframe = false;
      loginDisplay = false;
    
      constructor(private authService: MsalService) { }
    
      ngOnInit() {
        this.isIframe = window !== window.parent && !window.opener;
      }
    
      login() {
        this.authService.loginPopup()
          .subscribe({
            next: (result) => {
              console.log(result);
              this.setLoginDisplay();
            },
            error: (error) => console.log(error)
          });
      }
    
      setLoginDisplay() {
        this.loginDisplay = this.authService.instance.getAllAccounts().length > 0;
      }
    }
    

Вход с использованием перенаправлений

  1. Обновите src/app/app.module.ts, чтобы обеспечить начальную загрузку MsalRedirectComponent. Это выделенный компонент перенаправления, который обрабатывает перенаправления. Измените импорт и AppComponent начальную MsalModule загрузку следующим образом:

    ...
    import { MsalModule, MsalRedirectComponent } from '@azure/msal-angular'; // Updated import
    ...
      bootstrap: [AppComponent, MsalRedirectComponent] // MsalRedirectComponent bootstrapped here
    ...
    
  2. Откройте src/index.html и замените все содержимое файла следующим фрагментом кода, который добавляет <app-redirect> селектор:

    <!doctype html>
    <html lang="en">
    <head>
      <meta charset="utf-8">
      <title>msal-angular-tutorial</title>
      <base href="/">
      <meta name="viewport" content="width=device-width, initial-scale=1">
      <link rel="icon" type="image/x-icon" href="favicon.ico">
    </head>
    <body>
      <app-root></app-root>
      <app-redirect></app-redirect>
    </body>
    </html>
    
  3. Откройте src/app/app.component.ts и замените код следующим образом, чтобы выполнить вход пользователя с помощью перенаправления с полным кадром:

    import { MsalService } from '@azure/msal-angular';
    import { Component, OnInit } from '@angular/core';
    
    @Component({
      selector: 'app-root',
      templateUrl: './app.component.html',
      styleUrls: ['./app.component.css']
    })
    export class AppComponent implements OnInit {
      title = 'msal-angular-tutorial';
      isIframe = false;
      loginDisplay = false;
    
      constructor(private authService: MsalService) { }
    
      ngOnInit() {
        this.isIframe = window !== window.parent && !window.opener;
      }
    
      login() {
        this.authService.loginRedirect();
      }
    
      setLoginDisplay() {
        this.loginDisplay = this.authService.instance.getAllAccounts().length > 0;
      }
    }
    
  4. Перейдите к src/app/home/home.component.ts и замените все содержимое файла следующим фрагментом кода, чтобы подписаться на LOGIN_SUCCESS событие:

    import { Component, OnInit } from '@angular/core';
    import { MsalBroadcastService, MsalService } from '@azure/msal-angular';
    import { EventMessage, EventType, InteractionStatus } from '@azure/msal-browser';
    import { filter } from 'rxjs/operators';
    
    @Component({
      selector: 'app-home',
      templateUrl: './home.component.html',
      styleUrls: ['./home.component.css']
    })
    export class HomeComponent implements OnInit {
      constructor(private authService: MsalService, private msalBroadcastService: MsalBroadcastService) { }
    
      ngOnInit(): void {
        this.msalBroadcastService.msalSubject$
          .pipe(
            filter((msg: EventMessage) => msg.eventType === EventType.LOGIN_SUCCESS),
          )
          .subscribe((result: EventMessage) => {
            console.log(result);
          });
      }
    }
    

Условная отрисовка

Чтобы отобразить определенный пользовательский интерфейс (пользовательский интерфейс) только для прошедших проверку подлинности пользователей, компоненты должны подписаться на MsalBroadcastService приложение, чтобы узнать, выполнили ли пользователи вход и взаимодействие завершено.

  1. Добавьте MsalBroadcastService в файл src/app/app.component.ts и подпишитесь на наблюдаемый элемент inProgress$, чтобы перед отрисовкой пользовательского интерфейса проверить, завершено ли взаимодействие и выполнен ли вход в учетную запись. Код должен выглядеть следующим образом:

    import { Component, OnInit, OnDestroy } from '@angular/core';
    import { MsalService, MsalBroadcastService } from '@azure/msal-angular';
    import { InteractionStatus } from '@azure/msal-browser';
    import { Subject } from 'rxjs';
    import { filter, takeUntil } from 'rxjs/operators';
    
    @Component({
      selector: 'app-root',
      templateUrl: './app.component.html',
      styleUrls: ['./app.component.css']
    })
    export class AppComponent implements OnInit, OnDestroy {
      title = 'msal-angular-tutorial';
      isIframe = false;
      loginDisplay = false;
      private readonly _destroying$ = new Subject<void>();
    
      constructor(private broadcastService: MsalBroadcastService, private authService: MsalService) { }
    
      ngOnInit() {
        this.isIframe = window !== window.parent && !window.opener;
    
        this.broadcastService.inProgress$
        .pipe(
          filter((status: InteractionStatus) => status === InteractionStatus.None),
          takeUntil(this._destroying$)
        )
        .subscribe(() => {
          this.setLoginDisplay();
        })
      }
    
      login() {
        this.authService.loginRedirect();
      }
    
      setLoginDisplay() {
        this.loginDisplay = this.authService.instance.getAllAccounts().length > 0;
      }
    
      ngOnDestroy(): void {
        this._destroying$.next(undefined);
        this._destroying$.complete();
      }
    }
    
  2. Обновите код в файле src/app/home/home.component.ts, чтобы также проверить наличие взаимодействия, которое должно быть завершено перед обновлением пользовательского интерфейса. Код должен выглядеть следующим образом:

    import { Component, OnInit } from '@angular/core';
    import { MsalBroadcastService, MsalService } from '@azure/msal-angular';
    import { EventMessage, EventType, InteractionStatus } from '@azure/msal-browser';
    import { filter } from 'rxjs/operators';
    
    @Component({
      selector: 'app-home',
      templateUrl: './home.component.html',
      styleUrls: ['./home.component.css']
    })
    export class HomeComponent implements OnInit {
      loginDisplay = false;
    
      constructor(private authService: MsalService, private msalBroadcastService: MsalBroadcastService) { }
    
      ngOnInit(): void {
        this.msalBroadcastService.msalSubject$
          .pipe(
            filter((msg: EventMessage) => msg.eventType === EventType.LOGIN_SUCCESS),
          )
          .subscribe((result: EventMessage) => {
            console.log(result);
          });
    
        this.msalBroadcastService.inProgress$
          .pipe(
            filter((status: InteractionStatus) => status === InteractionStatus.None)
          )
          .subscribe(() => {
            this.setLoginDisplay();
          })
      }
    
      setLoginDisplay() {
        this.loginDisplay = this.authService.instance.getAllAccounts().length > 0;
      }
    }
    
  3. Замените код в файле src/app/home/home.component.html на следующие условные инструкции Display.

    <div *ngIf="!loginDisplay">
        <p>Please sign-in to see your profile information.</p>
    </div>
    
    <div *ngIf="loginDisplay">
        <p>Login successful!</p>
        <p>Request your profile information by clicking Profile above.</p>
    </div>
    

Реализация Angular Guard

Класс MsalGuard — это один из них, который можно использовать для защиты маршрутов и проверки подлинности перед доступом к защищенному маршруту. Ниже описано, как добавить MsalGuardProfile маршрут. Защита Profile маршрута означает, что даже если пользователь не войтет с помощью кнопки, если он пытается получить доступ к Profile маршруту или выбрать Profile кнопку, MsalGuard предложит пользователю пройти проверку подлинности с помощью Login всплывающего окна или перенаправления перед отображением Profile страницы.

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

  1. Добавьте в файл src/app/app.module.ts класс MsalGuard в качестве поставщика в приложении, а также добавьте конфигурации для MsalGuard. Области, необходимые для получения маркеров в дальнейшем, можно предоставить в authRequest, а в качестве типа взаимодействия для защиты можно установить Redirect или Popup. Код должен выглядеть так, как показано ниже.

    import { BrowserModule } from "@angular/platform-browser";
    import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
    import { NgModule } from "@angular/core";
    
    import { MatButtonModule } from "@angular/material/button";
    import { MatToolbarModule } from "@angular/material/toolbar";
    import { MatListModule } from "@angular/material/list";
    
    import { AppRoutingModule } from "./app-routing.module";
    import { AppComponent } from "./app.component";
    import { HomeComponent } from "./home/home.component";
    import { ProfileComponent } from "./profile/profile.component";
    
    import {
      MsalModule,
      MsalRedirectComponent,
      MsalGuard,
    } from "@azure/msal-angular"; // MsalGuard added to imports
    import {
      PublicClientApplication,
      InteractionType,
    } from "@azure/msal-browser"; // InteractionType added to imports
    
    const isIE =
      window.navigator.userAgent.indexOf("MSIE ") > -1 ||
      window.navigator.userAgent.indexOf("Trident/") > -1;
    
    @NgModule({
      declarations: [AppComponent, HomeComponent, ProfileComponent],
      imports: [
        BrowserModule,
        BrowserAnimationsModule,
        AppRoutingModule,
        MatButtonModule,
        MatToolbarModule,
        MatListModule,
        MsalModule.forRoot(
          new PublicClientApplication({
            auth: {
              clientId: "Enter_the_Application_Id_here",
              authority:
                "Enter_the_Cloud_Instance_Id_Here/Enter_the_Tenant_Info_Here",
              redirectUri: "Enter_the_Redirect_Uri_Here",
            },
            cache: {
              cacheLocation: "localStorage",
              storeAuthStateInCookie: isIE,
            },
          }),
          {
            interactionType: InteractionType.Redirect, // MSAL Guard Configuration
            authRequest: {
              scopes: ["user.read"],
            },
          },
          null
        ),
      ],
      providers: [
        MsalGuard, // MsalGuard added as provider here
      ],
      bootstrap: [AppComponent, MsalRedirectComponent],
    })
    export class AppModule {}
    
  2. В файле src/app/app-routing.module.ts задайте MsalGuard для маршрутов, которые необходимо защитить.

    import { NgModule } from "@angular/core";
    import { Routes, RouterModule } from "@angular/router";
    import { BrowserUtils } from "@azure/msal-browser";
    import { HomeComponent } from "./home/home.component";
    import { ProfileComponent } from "./profile/profile.component";
    import { MsalGuard } from "@azure/msal-angular";
    
    const routes: Routes = [
      {
        path: "profile",
        component: ProfileComponent,
        canActivate: [MsalGuard],
      },
      {
        path: "",
        component: HomeComponent,
      },
    ];
    
    const isIframe = window !== window.parent && !window.opener;
    
    @NgModule({
      imports: [
        RouterModule.forRoot(routes, {
          // Don't perform initial navigation in iframes or popups
          initialNavigation:
            !BrowserUtils.isInIframe() && !BrowserUtils.isInPopup()
              ? "enabledNonBlocking"
              : "disabled", // Set to enabledBlocking to use Angular Universal
        }),
      ],
      exports: [RouterModule],
    })
    export class AppRoutingModule {}
    
  3. Настройте вызовы для входа в файле src/app/app.component.ts, чтобы перенести в учетную запись набор authRequest в конфигурации защиты. Теперь код должен выглядеть так, как показано ниже.

    import { Component, OnInit, OnDestroy, Inject } from '@angular/core';
    import { MsalService, MsalBroadcastService, MSAL_GUARD_CONFIG, MsalGuardConfiguration } from '@azure/msal-angular';
    import { InteractionStatus, RedirectRequest } from '@azure/msal-browser';
    import { Subject } from 'rxjs';
    import { filter, takeUntil } from 'rxjs/operators';
    
    @Component({
      selector: 'app-root',
      templateUrl: './app.component.html',
      styleUrls: ['./app.component.css']
    })
    export class AppComponent implements OnInit, OnDestroy {
      title = 'msal-angular-tutorial';
      isIframe = false;
      loginDisplay = false;
      private readonly _destroying$ = new Subject<void>();
    
      constructor(@Inject(MSAL_GUARD_CONFIG) private msalGuardConfig: MsalGuardConfiguration, private broadcastService: MsalBroadcastService, private authService: MsalService) { }
    
      ngOnInit() {
        this.isIframe = window !== window.parent && !window.opener;
    
        this.broadcastService.inProgress$
        .pipe(
          filter((status: InteractionStatus) => status === InteractionStatus.None),
          takeUntil(this._destroying$)
        )
        .subscribe(() => {
          this.setLoginDisplay();
        })
      }
    
      login() {
        if (this.msalGuardConfig.authRequest){
          this.authService.loginRedirect({...this.msalGuardConfig.authRequest} as RedirectRequest);
        } else {
          this.authService.loginRedirect();
        }
      }
    
      setLoginDisplay() {
        this.loginDisplay = this.authService.instance.getAllAccounts().length > 0;
      }
    
      ngOnDestroy(): void {
        this._destroying$.next(undefined);
        this._destroying$.complete();
      }
    }
    

Получение маркера

Перехватчик Angular

MSAL Angular предоставляет класс Interceptor, который автоматически получает маркеры для исходящих запросов, которые используют клиент Angular http для известных защищенных ресурсов.

  1. Добавьте в файл src/app/app.module.ts класс Interceptor в качестве поставщика в приложении вместе с соответствующими конфигурациями. Теперь код должен выглядеть так, как показано ниже.

    import { BrowserModule } from "@angular/platform-browser";
    import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
    import { NgModule } from "@angular/core";
    import { HTTP_INTERCEPTORS, HttpClientModule } from "@angular/common/http"; // Import
    
    import { MatButtonModule } from "@angular/material/button";
    import { MatToolbarModule } from "@angular/material/toolbar";
    import { MatListModule } from "@angular/material/list";
    
    import { AppRoutingModule } from "./app-routing.module";
    import { AppComponent } from "./app.component";
    import { HomeComponent } from "./home/home.component";
    import { ProfileComponent } from "./profile/profile.component";
    
    import {
      MsalModule,
      MsalRedirectComponent,
      MsalGuard,
      MsalInterceptor,
    } from "@azure/msal-angular"; // Import MsalInterceptor
    import {
      InteractionType,
      PublicClientApplication,
    } from "@azure/msal-browser";
    
    const isIE =
      window.navigator.userAgent.indexOf("MSIE ") > -1 ||
      window.navigator.userAgent.indexOf("Trident/") > -1;
    
    @NgModule({
      declarations: [AppComponent, HomeComponent, ProfileComponent],
      imports: [
        BrowserModule,
        BrowserAnimationsModule,
        AppRoutingModule,
        MatButtonModule,
        MatToolbarModule,
        MatListModule,
        HttpClientModule,
        MsalModule.forRoot(
          new PublicClientApplication({
            auth: {
              clientId: "Enter_the_Application_Id_Here",
              authority:
                "Enter_the_Cloud_Instance_Id_Here/Enter_the_Tenant_Info_Here",
              redirectUri: "Enter_the_Redirect_Uri_Here",
            },
            cache: {
              cacheLocation: "localStorage",
              storeAuthStateInCookie: isIE,
            },
          }),
          {
            interactionType: InteractionType.Redirect,
            authRequest: {
              scopes: ["user.read"],
            },
          },
          {
            interactionType: InteractionType.Redirect, // MSAL Interceptor Configuration
            protectedResourceMap: new Map([
              ["Enter_the_Graph_Endpoint_Here/v1.0/me", ["user.read"]],
            ]),
          }
        ),
      ],
      providers: [
        {
          provide: HTTP_INTERCEPTORS,
          useClass: MsalInterceptor,
          multi: true,
        },
        MsalGuard,
      ],
      bootstrap: [AppComponent, MsalRedirectComponent],
    })
    export class AppModule {}
    

    Защищенные ресурсы предоставляются в виде protectedResourceMap. Для URL-адресов, которые вы указываете в коллекции protectedResourceMap, учитывается регистр. Для каждого ресурса добавьте области, которые должны быть возвращены в маркере доступа.

    Например:

    • ["user.read"] для Microsoft Graph;
    • ["<Application ID URL>/scope"] для настраиваемых веб-API (т. е. api://<Application ID>/access_as_user).

    Измените значения в protectedResourceMap, как описано далее.

    • Enter_the_Graph_Endpoint_Here экземпляр API Microsoft Graph, с которым должно взаимодействовать приложение. Для глобальной конечной точки API Microsoft Graph замените эту строку https://graph.microsoft.comна . Дополнительные сведения о конечных точках в национальных облачных развертываниях см. в статье Национальные облачные развертывания в документации по Microsoft Graph.
  2. Замените код в src/app/profile/profile.component.ts , чтобы получить профиль пользователя http-запросом и заменить GRAPH_ENDPOINT ее конечной точкой Microsoft Graph:

    import { Component, OnInit } from '@angular/core';
    import { HttpClient } from '@angular/common/http';
    
    const GRAPH_ENDPOINT = 'Enter_the_Graph_Endpoint_Here/v1.0/me';
    
    type ProfileType = {
      givenName?: string,
      surname?: string,
      userPrincipalName?: string,
      id?: string
    };
    
    @Component({
      selector: 'app-profile',
      templateUrl: './profile.component.html',
      styleUrls: ['./profile.component.css']
    })
    export class ProfileComponent implements OnInit {
      profile!: ProfileType;
    
      constructor(
        private http: HttpClient
      ) { }
    
      ngOnInit() {
        this.getProfile();
      }
    
      getProfile() {
        this.http.get(GRAPH_ENDPOINT)
          .subscribe(profile => {
            this.profile = profile;
          });
      }
    }
    
  3. Замените пользовательский интерфейс в файле src/app/profile/profile.component.html, чтобы отобразить сведения о профиле.

    <div>
        <p><strong>First Name: </strong> {{profile?.givenName}}</p>
        <p><strong>Last Name: </strong> {{profile?.surname}}</p>
        <p><strong>Email: </strong> {{profile?.userPrincipalName}}</p>
        <p><strong>Id: </strong> {{profile?.id}}</p>
    </div>
    

Выйти

  1. Обновите код в файле src/app/app.component.html, чтобы кнопка Logout отображалась только при выполнении условия.

    <mat-toolbar color="primary">
      <a class="title" href="/">{{ title }}</a>
    
      <div class="toolbar-spacer"></div>
    
      <a mat-button [routerLink]="['profile']">Profile</a>
    
      <button mat-raised-button *ngIf="!loginDisplay" (click)="login()">Login</button>
      <button mat-raised-button *ngIf="loginDisplay" (click)="logout()">Logout</button>
    
    </mat-toolbar>
    <div class="container">
      <!--This is to avoid reload during acquireTokenSilent() because of hidden iframe -->
      <router-outlet *ngIf="!isIframe"></router-outlet>
    </div>
    

Выход с помощью перенаправлений

  1. Обновите код в файле src/app/app.component.ts, чтобы пользователь выходил из учетной записи с помощью перенаправлений.

    import { Component, OnInit, OnDestroy, Inject } from '@angular/core';
    import { MsalService, MsalBroadcastService, MSAL_GUARD_CONFIG, MsalGuardConfiguration } from '@azure/msal-angular';
    import { InteractionStatus, RedirectRequest } from '@azure/msal-browser';
    import { Subject } from 'rxjs';
    import { filter, takeUntil } from 'rxjs/operators';
    
    @Component({
      selector: 'app-root',
      templateUrl: './app.component.html',
      styleUrls: ['./app.component.css']
    })
    export class AppComponent implements OnInit, OnDestroy {
      title = 'msal-angular-tutorial';
      isIframe = false;
      loginDisplay = false;
      private readonly _destroying$ = new Subject<void>();
    
      constructor(@Inject(MSAL_GUARD_CONFIG) private msalGuardConfig: MsalGuardConfiguration, private broadcastService: MsalBroadcastService, private authService: MsalService) { }
    
      ngOnInit() {
        this.isIframe = window !== window.parent && !window.opener;
    
        this.broadcastService.inProgress$
        .pipe(
          filter((status: InteractionStatus) => status === InteractionStatus.None),
          takeUntil(this._destroying$)
        )
        .subscribe(() => {
          this.setLoginDisplay();
        })
      }
    
      login() {
        if (this.msalGuardConfig.authRequest){
          this.authService.loginRedirect({...this.msalGuardConfig.authRequest} as RedirectRequest);
        } else {
          this.authService.loginRedirect();
        }
      }
    
      logout() { // Add log out function here
        this.authService.logoutRedirect({
          postLogoutRedirectUri: 'http://localhost:4200'
        });
      }
    
      setLoginDisplay() {
        this.loginDisplay = this.authService.instance.getAllAccounts().length > 0;
      }
    
      ngOnDestroy(): void {
        this._destroying$.next(undefined);
        this._destroying$.complete();
      }
    }
    

Выход с помощью всплывающих окон

  1. Обновите код в файле src/app/app.component.ts, чтобы пользователь выходил из учетной записи с помощью всплывающих окон.

    import { Component, OnInit, OnDestroy, Inject } from '@angular/core';
    import { MsalService, MsalBroadcastService, MSAL_GUARD_CONFIG, MsalGuardConfiguration } from '@azure/msal-angular';
    import { InteractionStatus, PopupRequest } from '@azure/msal-browser';
    import { Subject } from 'rxjs';
    import { filter, takeUntil } from 'rxjs/operators';
    
    @Component({
      selector: 'app-root',
      templateUrl: './app.component.html',
      styleUrls: ['./app.component.css']
    })
    export class AppComponent implements OnInit, OnDestroy {
      title = 'msal-angular-tutorial';
      isIframe = false;
      loginDisplay = false;
      private readonly _destroying$ = new Subject<void>();
    
      constructor(@Inject(MSAL_GUARD_CONFIG) private msalGuardConfig: MsalGuardConfiguration, private broadcastService: MsalBroadcastService, private authService: MsalService) { }
    
      ngOnInit() {
        this.isIframe = window !== window.parent && !window.opener;
    
        this.broadcastService.inProgress$
        .pipe(
          filter((status: InteractionStatus) => status === InteractionStatus.None),
          takeUntil(this._destroying$)
        )
        .subscribe(() => {
          this.setLoginDisplay();
        })
      }
    
      login() {
        if (this.msalGuardConfig.authRequest){
          this.authService.loginPopup({...this.msalGuardConfig.authRequest} as PopupRequest)
            .subscribe({
              next: (result) => {
                console.log(result);
                this.setLoginDisplay();
              },
              error: (error) => console.log(error)
            });
        } else {
          this.authService.loginPopup()
            .subscribe({
              next: (result) => {
                console.log(result);
                this.setLoginDisplay();
              },
              error: (error) => console.log(error)
            });
        }
      }
    
      logout() { // Add log out function here
        this.authService.logoutPopup({
          mainWindowRedirectUri: "/"
        });
      }
    
      setLoginDisplay() {
        this.loginDisplay = this.authService.instance.getAllAccounts().length > 0;
      }
    
      ngOnDestroy(): void {
        this._destroying$.next(undefined);
        this._destroying$.complete();
      }
    }
    

Тестирование кода

  1. Запустите веб-сервер для прослушивания порта, выполнив приведенные ниже команды в командной строке из папки приложения:

    npm install
    npm start
    
  2. В браузере введите http://localhost:4200и увидите страницу, которая выглядит следующим образом.

    Web browser displaying sign-in dialog

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

    Content dialog displayed in web browser

  4. После предоставления согласия в следующем случае при согласии на запрошенные разрешения веб-приложение отображает страницу успешного входа.

    Results of a successful sign-in in the web browser

  5. Выберите профиль , чтобы просмотреть сведения о профиле пользователя, возвращенные в ответе на вызов API Microsoft Graph:

    Profile information from Microsoft Graph displayed in the browser

Добавление областей и делегированных разрешений

Для чтения профиля пользователя API Microsoft Graph требуется область User.Read. Область User.Read добавляется автоматически в каждую регистрацию приложения. Другие API для Microsoft Graph и пользовательские API для внутреннего сервера могут потребовать других область. Например, для отображения сообщений электронной почты пользователя API Microsoft Graph требуется область Mail.Read.

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

Примечание.

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

Справка и поддержка

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

Следующие шаги

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