Compartir a través de


Tutorial: Inicio de sesión de usuarios y llamada a Microsoft Graph API desde una aplicación de página única (SPA) de Angular mediante el uso del flujo de código de autenticación

En este tutorial, compilarás una aplicación de página única (SPA) de Angular que inicia la sesión de los usuarios y llama a la API de Microsoft Graph mediante el flujo de código de autorización con clave de prueba para intercambio de código (PKCE). La aplicación de página única que cree usa la biblioteca de autenticación de Microsoft (MSAL) para Angular v2.

En este tutorial, aprenderá a:

  • Registro de la aplicación en el centro de administración de Microsoft Entra
  • Crear un proyecto de Angular con npm
  • Agregar código para admitir el inicio y el cierre de sesión de usuario
  • Agregar código para llamar a Microsoft Graph API
  • Prueba de la aplicación

MSAL Angular v2 utiliza el flujo de código de autorización con PKCE en el explorador, lo que supone una mejora respecto a MSAL Angular v1, que utilizaba el flujo de concesión implícita. Se recomienda usar el flujo de código de autorización con PKCE para aplicaciones de página única (SPA) porque es más seguro que el flujo implícito. MSAL Angular v2 no admite el flujo implícito.

Prerrequisitos

Funcionamiento de la aplicación de ejemplo

Diagrama que muestra el flujo de código de autorización para una aplicación de página única

La aplicación de ejemplo que se crea con este tutorial permite que una aplicación de página única de Angular haga consultas a Microsoft Graph API o a una API web que acepte los tokens que emite la Plataforma de identidad de Microsoft. Usa la Biblioteca de autenticación de Microsoft (MSAL) para Angular v2, un contenedor de la biblioteca principal de MSAL.js v2. MSAL Angular permite que las aplicaciones de Angular 9 y versiones posteriores autentiquen a usuarios empresariales mediante Microsoft Entra ID, a usuarios de cuentas de Microsoft y a usuarios de identidades de redes sociales como Facebook, Google y LinkedIn. La biblioteca también permite que las aplicaciones obtengan acceso a los servicios en la nube de Microsoft y a Microsoft Graph.

En este escenario, después de que un usuario inicia sesión, se solicita un token de acceso y se agrega a las solicitudes HTTP mediante el encabezado de autorización. MSAL controla la adquisición y la renovación de tokens.

Bibliotecas

En este tutorial se usan las siguientes bibliotecas:

Biblioteca Descripción
MSAL Angular Biblioteca de autenticación de Microsoft para el contenedor de Angular de JavaScript
Explorador MSAL Biblioteca de autenticación de Microsoft para el paquete del explorador de JavaScript v2

Puede encontrar el código fuente de todas las bibliotecas MSAL.js en el repositorio microsoft-authentication-library-for-js en GitHub.

Obtención del ejemplo de código completado

¿Prefiere descargar el proyecto de muestra completado para este tutorial? Clonación del ms-identity-javascript-angular-spa

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

Para continuar con el tutorial y compilar la aplicación, ve a la siguiente sección, Registro de la aplicación y registro de los identificadores .

Registro de la aplicación y los identificadores de registro

Sugerencia

Los pasos de este artículo pueden variar ligeramente en función del portal desde donde comienza.

Para completar el registro, proporcione un nombre a la aplicación, especifique los tipos de cuenta admitidos y agregue un URI de redireccionamiento. Una vez registrada, el panel Información general de la aplicación muestra los identificadores necesarios en el código fuente de la aplicación.

  1. Inicie sesión en el Centro de administración de Microsoft Entra al menos como Desarrollador de aplicaciones.
  2. Si tiene acceso a varios inquilinos, use el icono Configuración del menú superior para cambiar al inquilino en el que desea registrar la aplicación desde el menú Directorios y suscripciones.
  3. Vaya aIdentidad>Aplicaciones>Registros de aplicaciones.
  4. Seleccione Nuevo registro.
  5. Escriba un nombre para la aplicación, como Angular-SPA-auth-code.
  6. Para la opción Tipos de cuenta admitidos, seleccione Solo las cuentas de este directorio organizativo. Para obtener información sobre los distintos tipos de cuenta, selecciona la opción Ayudarme a elegir.
  7. En URI de redireccionamiento (opcional), use el menú desplegable para seleccionar Aplicación de página única (SPA) y escriba http://localhost:4200 en el cuadro de texto.
  8. Seleccione Registrar.
  9. El panel Información general de la aplicación se muestra cuando se completa el registro. Registrar el id. de directorio (inquilino) y el id. de aplicación (cliente) que se usará en el código fuente de la aplicación.

Creación del proyecto

  1. Abra Visual Studio Code.

  2. Selecciona Archivo>Abrir carpeta.... Navega y selecciona la ubicación en la que se va a crear el proyecto.

  3. Abra una terminal nueva seleccionando Terminal >Crear terminal.

    1. Es posible que tenga que modificar los tipos de terminal. Seleccione la flecha abajo situada junto al icono + del terminal y seleccione Símbolo del sistema.
  4. Ejecute los siguientes comandos para crear un nuevo proyecto de Angular con el nombre msal-angular-tutorial, instale bibliotecas de componentes de material de Angular, MSAL Browser, MSAL Angular y genere componentes de inicio y perfil.

    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
    

Configuración de la aplicación y edición de la interfaz de usuario base

  1. Abra src/app/app.module.ts. El MsalModule y MsalInterceptor deberán agregarse a imports junto con la constante isIE. También agregarás los módulos de material. Reemplace todo el contenido del archivo de código por el siguiente fragmento de código:

    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. Reemplace los siguientes valores por los valores obtenidos del Centro de administración de Microsoft Entra. Para más información acerca de las opciones configurables disponibles, consulte Inicializar aplicaciones cliente.

    • clientId - El identificador de la aplicación, también denominado cliente. Reemplace Enter_the_Application_Id_Here por el valor de Id. de la aplicación (cliente) que se registró anteriormente en la página de resumen de la aplicación registrada.
    • authority - Se compone de dos partes:
      • La instancia es el punto de conexión del proveedor de nube. En el caso de la nube principal o global de Azure, escriba https://login.microsoftonline.com. Consulte los diferentes puntos de conexión disponibles en nubes nacionales.
      • El Id. de inquilino es el identificador del inquilino en el que está registrada la solicitud. Reemplace el _Enter_the_Tenant_Info_Here con el valor de Id. de directorio (inquilino) que se registró anteriormente en la página de resumen de la aplicación registrada.
    • redirectUri: la ubicación a la que el servidor de autorización envía al usuario una vez que se haya autorizado correctamente a la aplicación y se haya concedido un código de autorización o un token de acceso. Reemplace Enter_the_Redirect_Uri_Here por http://localhost:4200.
  3. Abra src/app/app-routing.module.ts y agregue rutas a los componentes de inicio y perfil. Reemplace todo el contenido del archivo de código por el siguiente fragmento de código:

    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. Abre src/app/app.component.html y reemplaza el código existente por el siguiente fragmento de código:

    <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. Abra src/style.css para definir el 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. Abra src/app/app.component.css para agregar estilos CSS a la aplicación:

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

Inicio de sesión mediante elementos emergentes

  1. Abre src/app/app.component.ts y reemplaza el contenido del archivo por el siguiente fragmento de código para iniciar sesión a un usuario mediante una ventana emergente:

    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;
      }
    }
    

Inicio de sesión mediante redireccionamientos

  1. Actualice src/app/app.module.ts para arrancar MsalRedirectComponent. Se trata de un componente de redireccionamiento dedicado, el cual controla las redirecciones. Cambia la importación MsalModule y el arranque AppComponent para que se parezcan al siguiente fragmento de código:

    ...
    import { MsalModule, MsalRedirectComponent } from '@azure/msal-angular'; // Updated import
    ...
      bootstrap: [AppComponent, MsalRedirectComponent] // MsalRedirectComponent bootstrapped here
    ...
    
  2. Abra src/index.html y reemplace todo el contenido del archivo por el siguiente fragmento de código, el cual agrega el selector <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. Abre src/app/app.component.ts y reemplaza el código por el siguiente fragmento de código para iniciar sesión a un usuario mediante una redirección de marco completo:

    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. Abre src/app/home/home.component.ts y reemplaza todo el contenido del archivo por el siguiente fragmento de código para suscribirte al evento 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);
          });
      }
    }
    

Representación condicional

Para asegurarte de que determinados componentes de la interfaz de usuario (IU) solo se muestran para los usuarios autenticados, los componentes deben suscribirse a MsalBroadcastService para comprobar si los usuarios han iniciado sesión y si la interacción está completa.

  1. Agregue MsalBroadcastService a src/app/app.component.ts y suscríbase al objeto observable inProgress$ para comprobar si la interacción se ha completado y una cuenta ha iniciado sesión antes de representar la interfaz de usuario. Ahora el código debería ser similar a este:

    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. Actualice el código de src/app/home/home.component.ts para comprobar también si la interacción se completará antes de actualizar la interfaz de usuario. Ahora el código debería ser similar a este:

    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. Reemplace el código de src/app/home/home.component.html por las siguientes pantallas condicionales:

    <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>
    

Implementación de Angular Guard

La clase MsalGuard es una que puede usar para proteger las rutas y requerir autenticación antes de acceder a la ruta protegida. En los pasos siguientes, agregue MsalGuard a la ruta Profile. Proteger la ruta Profile significa que, aunque un usuario no inicie sesión con el botón Login, si intenta acceder a la ruta Profile o seleccionar el botón Profile, MsalGuard le solicitará que se autentique mediante un elemento emergente o un redireccionamiento antes de mostrar la página Profile.

MsalGuard es una clase de utilidad que puede usar para mejorar la experiencia del usuario, pero no debería basarse en ella por seguridad. Los atacantes podría moverse por las protecciones del lado cliente y usted debería asegurarse de que el servidor no devuelva ningún dato al que el usuario no debiera acceder.

  1. Agregue la clase MsalGuard como proveedor en la aplicación en src/app/app.module.tsy agregue las configuraciones de MsalGuard. Los ámbitos necesarios para adquirir tokens más adelante se pueden proporcionar en authRequest y el tipo de interacción para la protección se puede establecer en Redirect o Popup. El código debería parecerse al siguiente fragmento de código:

    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. Establezca MsalGuard las rutas que desea proteger en 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";
    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. Ajusta las llamadas de inicio de sesión en src/app/app.component.ts para tener en cuenta el conjunto de authRequest en las configuraciones de restricción. El código debería parecerse ahora al siguiente fragmento de código:

    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();
      }
    }
    

Adquisición de un token

Interceptor de Angular

MSAL Angular proporciona una clase Interceptor que adquiere automáticamente tokens para las solicitudes salientes que usan el cliente http de Angular con los recursos protegidos conocidos.

  1. Agregue la clase Interceptor como proveedor a la aplicación en src/app/app.module.ts, con sus configuraciones. El código debería parecerse ahora al siguiente fragmento de código:

    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 {}
    

    Los recursos protegidos se proporcionan como protectedResourceMap. Las direcciones URL que proporciona en la colección protectedResourceMap distinguen mayúsculas de minúsculas. Para cada recurso, agregue los ámbitos cuya devolución se solicita en el token de acceso.

    Por ejemplo:

    • ["user.read"] para Microsoft Graph
    • ["<Application ID URL>/scope"] para las API web personalizadas (es decir, api://<Application ID>/access_as_user)

    Modifique los valores en la sección protectedResourceMap, como se describe aquí:

    • Enter_the_Graph_Endpoint_Here es la instancia de Microsoft Graph API con la que la aplicación debe comunicarse. En el caso del punto de conexión de Microsoft Graph API global, puede reemplazar esta cadena por https://graph.microsoft.com. Para obtener información sobre los puntos de conexión en las implementaciones de nubes nacionales, consulte Implementaciones de nube nacionales en la documentación de Microsoft Graph.
  2. Reemplace el código de src/app/profile/profile.component.ts para recuperar el perfil de un usuario con una solicitud HTTP, y reemplace GRAPH_ENDPOINT por el punto de conexión de 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. Reemplace la interfaz de usuario de src/app/profile/profile.component.html para mostrar la información del perfil:

    <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>
    

Cerrar sesión

  1. Actualice el código de src/app/app.component.html para mostrar condicionalmente un botón 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>
    

Cierre de sesión mediante redireccionamientos

  1. Actualice el código de src/app/app.component.ts para cerrar la sesión de un usuario mediante redirecciones:

    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();
      }
    }
    

Cierre de sesión mediante elementos emergentes

  1. Actualice el código de src/app/app.component.ts para cerrar la sesión de un usuario mediante elementos emergentes:

    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();
      }
    }
    

Prueba del código

  1. Inicie el servidor web para que escuche el puerto; para ello, ejecute los siguientes comandos desde la carpeta de la aplicación en un símbolo del sistema de la línea de comandos:

    npm install
    npm start
    
  2. En el explorador, escriba http://localhost:4200 y, a continuación, debería ver una página similar a la siguiente.

    Explorador web que muestra el cuadro de diálogo de inicio de sesión

  3. Seleccione Aceptar para conceder los permisos de aplicación al perfil. Esto ocurrirá la primera vez que empieces a iniciar sesión.

    Cuadro de diálogo de contenido que se muestra en el explorador web

  4. Si das tu consentimiento a los permisos solicitados, la aplicación web mostrará una página que indica que el inicio de sesión se ha realizado correctamente.

    Resultados de un inicio de sesión correcto en el explorador web

  5. Seleccione Perfil para ver la información de perfil de usuario devuelta en la respuesta de la llamada a Microsoft Graph API:

    Información de perfil de Microsoft Graph que se muestra en el explorador

Incorporación de ámbitos y permisos delegados

La API de Microsoft Graph requiere el ámbito User.Read para leer el perfil de un usuario. El ámbito User.Read se agrega automáticamente a cada registro de aplicación. Otras API de Microsoft Graph, así como las API personalizadas para el servidor back-end, podrían requerir otros ámbitos. Por ejemplo, la API de Microsoft Graph requiere el ámbito Mail.Read para mostrar el correo electrónico del usuario.

A medida que se agreguen ámbitos, puede que se pida a los usuarios que proporcionen consentimiento adicional para los ámbitos agregados.

Nota:

Es posible que se pida al usuario algún consentimiento adicional a medida que aumente el número de ámbitos.

Ayuda y soporte técnico

Si necesita ayuda, desea informar de un problema o desea obtener información sobre las opciones de soporte técnico, consulte Opciones de ayuda y soporte técnico para desarrolladores.

Pasos siguientes

  • Para obtener más información, cree una aplicación de página única (SPA) de React que inicie sesión de los usuarios en la siguiente serie de tutoriales de varias partes.