Ejercicio: Adición de la autenticación de usuario

Completado

La aplicación web de lista de la compra requiere la autenticación de usuario. En este ejercicio, implementará el inicio y el cierre de sesión en la aplicación y mostrará el estado de inicio de sesión del usuario actual.

En este ejercicio, realizará los pasos siguientes:

  1. Instalar la CLI de Static Web Apps para el desarrollo local
  2. Ejecutar la aplicación y la API de forma local con emulación de autenticación local
  3. Agregar botones de inicio de sesión para varios proveedores de autenticación
  4. Agregar un botón de cierre de sesión si el usuario ha iniciado sesión
  5. Mostrar el estado de inicio de sesión del usuario
  6. Probar el flujo de trabajo de autenticación de forma local
  7. Implementar la aplicación actualizada

Preparación para el desarrollo local

La CLI de Static Web Apps, también conocida como CLI de SWA, es una herramienta de desarrollo local que le permite ejecutar la aplicación web y la API de forma local y emular los servidores de autenticación y autorización.

  1. Abra un terminal en el equipo.

  2. Ejecute el comando siguiente para instalar la CLI de SWA.

    npm install -g @azure/static-web-apps-cli
    

Probar la aplicación localmente

Ahora, ejecute la aplicación y la API de forma local con un servidor de desarrollo. De este modo, podrá ver y probar los cambios a medida que los realice en el código.

  1. Abra el proyecto en Visual Studio Code.

  2. En Visual Studio Code, presione F1 para abrir la paleta de comandos.

  3. Escriba y seleccione Terminal: Crear nuevo terminal integrado.

  4. Después, vaya a la carpeta de su marco de front-end de preferencia, como se muestra a continuación:

    cd angular-app
    
    cd react-app
    
    cd svelte-app
    
    cd vue-app
    
  5. Ejecute la aplicación cliente de front-end con un servidor de desarrollo.

    npm start
    
    npm start
    
    npm run dev
    
    npm run serve
    

    Deje el servidor en ejecución en segundo plano. Ahora ejecute el emulador del servidor de autenticación y la API mediante la CLI de SWA.

  6. En Visual Studio Code, presione F1 para abrir la paleta de comandos.

  7. Escriba y seleccione Terminal: Crear nuevo terminal integrado.

  8. Ejecute la CLI de SWA mediante el siguiente comando:

    swa start http://localhost:4200 --api-location ./api
    
    swa start http://localhost:3000 --api-location ./api
    
    swa start http://localhost:5000 --api-location ./api
    
    swa start http://localhost:8080 --api-location ./api
    
  9. Vaya a http://localhost:4280.

El puerto final que usa la CLI de SWA es diferente del que ha visto antes, ya que usa un proxy inverso para reenviar las solicitudes a los tres componentes distintos:

  • El servidor de desarrollo de marcos
  • El emulador de autenticación y autorización
  • La API hospedada por el entorno de ejecución de Functions

Captura de pantalla de la arquitectura de la CLI de Static Web Apps.

Deje que la aplicación permanezca en ejecución mientras modifica el código.

Obtención del estado de inicio de sesión del usuario

En primer lugar, tendrá que acceder al estado de inicio de sesión del usuario mediante una consulta a /.auth/me en el cliente.

  1. Cree el archivo angular-app/src/app/core/models/user-info.ts y agregue el código siguiente a fin de representar la interfaz para la información del usuario.

    export interface UserInfo {
      identityProvider: string;
      userId: string;
      userDetails: string;
      userRoles: string[];
    }
    
  2. Edite el archivo angular-app/src/app/core/components/nav.component.ts y agregue el siguiente método a la clase NavComponent.

    async getUserInfo() {
      try {
        const response = await fetch('/.auth/me');
        const payload = await response.json();
        const { clientPrincipal } = payload;
        return clientPrincipal;
      } catch (error) {
        console.error('No profile could be found');
        return undefined;
      }
    }
    
  3. Cree una propiedad de clase userInfo y almacene el resultado de la función asincrónica getUserInfo() cuando se inicialice el componente. Implemente la interfaz OnInit y actualice las instrucciones import para importar OnInit y UserInfo. Este código captura la información del usuario cuando se inicializa el componente.

    import { Component, OnInit } from '@angular/core';
    import { UserInfo } from '../model/user-info';
    
    export class NavComponent implements OnInit {
      userInfo: UserInfo;
    
      async ngOnInit() {
        this.userInfo = await this.getUserInfo();
      }
      // ...
    }
    
  1. Edite el archivo react-app/src/components/NavBar.js y agregue el código siguiente en la parte superior de la función. Este código captura la información del usuario al cargarse el componente y la almacena en el estado.

    import React, { useState, useEffect } from 'react';
    import { NavLink } from 'react-router-dom';
    
    const NavBar = (props) => {
      const [userInfo, setUserInfo] = useState();
    
      useEffect(() => {
        (async () => {
          setUserInfo(await getUserInfo());
        })();
      }, []);
    
      async function getUserInfo() {
        try {
          const response = await fetch('/.auth/me');
          const payload = await response.json();
          const { clientPrincipal } = payload;
          return clientPrincipal;
        } catch (error) {
          console.error('No profile could be found');
          return undefined;
        }
      }
    
      return (
      // ...
    
  1. Edite el archivo svelte-app/src/components/NavBar.svelte y agregue el siguiente código en la sección del script. Este código captura la información del usuario cuando se carga el componente.

    import { onMount } from 'svelte';
    
    let userInfo = undefined;
    
    onMount(async () => (userInfo = await getUserInfo()));
    
    async function getUserInfo() {
      try {
        const response = await fetch('/.auth/me');
        const payload = await response.json();
        const { clientPrincipal } = payload;
        return clientPrincipal;
      } catch (error) {
        console.error('No profile could be found');
        return undefined;
      }
    }
    
  1. Edite el archivo vue-app/src/components/nav-bar.vue y agregue userInfo al objeto de datos.

     data() {
       return {
         userInfo: {
           type: Object,
           default() {},
         },
       };
     },
    
  2. Agregue el método getUserInfo() a la sección methods.

    methods: {
      async getUserInfo() {
        try {
          const response = await fetch('/.auth/me');
          const payload = await response.json();
          const { clientPrincipal } = payload;
          return clientPrincipal;
        } catch (error) {
          console.error('No profile could be found');
          return undefined;
        }
      },
    },
    
  3. Agregue el enlace de ciclo de vida created al componente.

    async created() {
      this.userInfo = await this.getUserInfo();
    },
    

    Al crear el componente, la información del usuario se captura automáticamente.

Adición de los botones de inicio y cierre de sesión

La información del usuario será undefined si no ha iniciado sesión, de modo que los cambios no serán visibles por ahora. Es el momento de agregar botones de inicio de sesión para los distintos proveedores.

  1. Edite el archivo angular-app/src/app/core/components/nav.component.ts para agregar una lista de proveedores a la clase NavComponent.

    providers = ['x', 'github', 'aad'];
    
  2. Agregue la siguiente propiedad redirect a fin de capturar la dirección URL actual para el redireccionamiento posterior al inicio de sesión.

    redirect = window.location.pathname;
    
  3. Agregue el código siguiente a la plantilla, después del primer elemento </nav>, para mostrar los botones de inicio y cierre de sesión.

    <nav class="menu auth">
      <p class="menu-label">Auth</p>
      <div class="menu-list auth">
        <ng-container *ngIf="!userInfo; else logout">
          <ng-container *ngFor="let provider of providers">
            <a href="/.auth/login/{{provider}}?post_login_redirect_uri={{redirect}}">{{provider}}</a>
          </ng-container>
        </ng-container>
        <ng-template #logout>
          <a href="/.auth/logout?post_logout_redirect_uri={{redirect}}">Logout</a>
        </ng-template>
      </div>
    </nav>
    

    Si el usuario no ha iniciado sesión, muestre el botón de inicio de sesión de cada proveedor. Cada botón se vincula a /.auth/login/<AUTH_PROVIDER> y establece la dirección URL de redireccionamiento a la página actual.

    De lo contrario, si el usuario ya ha iniciado sesión, se muestra un botón de cierre de sesión que se vincula a /.auth/logout y también establece la dirección URL de redireccionamiento a la página actual.

Ahora, debería ver esta página web en el explorador.

Captura de pantalla de la aplicación web Angular con botones de inicio de sesión.

  1. Edite el archivo react-app/src/components/NavBar.js para agregar una lista de proveedores en la parte superior de la función.

    const providers = ['x', 'github', 'aad'];
    
  2. Agregue la siguiente variable redirect debajo de la primera variable para capturar la dirección URL actual para el redireccionamiento posterior al inicio de sesión.

    const redirect = window.location.pathname;
    
  3. Agregue el código siguiente a la plantilla JSX, después del primer elemento </nav>, para mostrar los botones de inicio y de cierre de sesión.

    <nav className="menu auth">
      <p className="menu-label">Auth</p>
      <div className="menu-list auth">
        {!userInfo &&
          providers.map((provider) => (
            <a key={provider} href={`/.auth/login/${provider}?post_login_redirect_uri=${redirect}`}>
              {provider}
            </a>
          ))}
        {userInfo && <a href={`/.auth/logout?post_logout_redirect_uri=${redirect}`}>Logout</a>}
      </div>
    </nav>
    

    Si el usuario no ha iniciado sesión, muestre el botón de inicio de sesión de cada proveedor. Cada botón se vincula a /.auth/login/<AUTH_PROVIDER> y establece la dirección URL de redireccionamiento a la página actual.

    De lo contrario, si el usuario ya ha iniciado sesión, muestre un botón de cierre de sesión que se vincula a /.auth/logout y también establece la dirección URL de redireccionamiento a la página actual.

Ahora, debería ver esta página web en el explorador.

Captura de pantalla de la aplicación web React con botones de inicio de sesión.

  1. Edite el archivo svelte-app/src/components/NavBar.svelte para agregar una lista de proveedores en la parte superior del script.

    const providers = ['x', 'github', 'aad'];
    
  2. Agregue la siguiente variable redirect debajo de la primera variable para capturar la dirección URL actual para el redireccionamiento posterior al inicio de sesión.

    const redirect = window.location.pathname;
    
  3. Agregue el código siguiente a la plantilla, después del primer elemento </nav>, para mostrar los botones de inicio y cierre de sesión.

     <nav class="menu auth">
       <p class="menu-label">Auth</p>
       <div class="menu-list auth">
         {#if !userInfo}
           {#each providers as provider (provider)}
             <a href={`/.auth/login/${provider}?post_login_redirect_uri=${redirect}`}>
               {provider}
             </a>
           {/each}
         {/if}
         {#if userInfo}
           <a href={`/.auth/logout?post_logout_redirect_uri=${redirect}`}>
             Logout
           </a>
         {/if}
       </div>
     </nav>
    

    Si el usuario no ha iniciado sesión, muestre el botón de inicio de sesión de cada proveedor. Cada botón se vincula a /.auth/login/<AUTH_PROVIDER> y establece la dirección URL de redireccionamiento a la página actual.

    De lo contrario, si el usuario ya ha iniciado sesión, muestre un botón de cierre de sesión que se vincula a /.auth/logout y también establece la dirección URL de redireccionamiento a la página actual.

Ahora, debería ver esta página web en el explorador.

Captura de pantalla de la aplicación web Svelte con botones de inicio de sesión.

  1. Edite el archivo vue-app/src/components/nav-bar.vue y agregue una lista de proveedores al objeto de datos.

     providers: ['x', 'github', 'aad'],
    
  2. Agregue la propiedad redirect a continuación a fin de capturar la dirección URL actual para el redireccionamiento posterior al inicio de sesión.

     redirect: window.location.pathname,
    
  3. Agregue el código siguiente a la plantilla, después del primer elemento </nav>, para mostrar los botones de inicio y cierre de sesión.

    <nav class="menu auth">
      <p class="menu-label">Auth</p>
      <div class="menu-list auth">
        <template v-if="!userInfo">
          <template v-for="provider in providers">
            <a :key="provider" :href="`/.auth/login/${provider}?post_login_redirect_uri=${redirect}`">
              {{ provider }}
            </a>
          </template>
        </template>
        <a v-if="userInfo" :href="`/.auth/login/${provider}?post_login_redirect_uri=${redirect}`"> Logout </a>
      </div>
    </nav>
    

    Si el usuario no ha iniciado sesión, muestre el botón de inicio de sesión de cada proveedor. Cada botón se vincula a /.auth/login/<AUTH_PROVIDER> y establece la dirección URL de redireccionamiento a la página actual.

    De lo contrario, si el usuario ya ha iniciado sesión, muestre un botón de cierre de sesión que se vincula a /.auth/logout y también establece la dirección URL de redireccionamiento a la página actual.

Ahora, debería ver esta página web en el explorador.

Captura de pantalla de la aplicación web Vue con botones de inicio de sesión.

Mostrar el estado de inicio de sesión del usuario

Antes de probar el flujo de trabajo de autenticación, se mostrarán los detalles del usuario que ha iniciado sesión.

Edite el archivo angular-app/src/app/core/components/nav.component.ts y agregue este código en la parte inferior de la plantilla después de la última etiqueta </nav> de cierre.

<div class="user" *ngIf="userInfo">
  <p>Welcome</p>
  <p>{{ userInfo?.userDetails }}</p>
  <p>{{ userInfo?.identityProvider }}</p>
</div>

Nota:

La propiedad userDetails puede ser un nombre de usuario o una dirección de correo electrónico, en función de la identidad proporcionada para iniciar sesión.

Una vez completado, el archivo debería tener el siguiente aspecto:

import { Component, OnInit } from '@angular/core';
import { UserInfo } from '../model/user-info';

@Component({
  selector: 'app-nav',
  template: `
    <nav class="menu">
      <p class="menu-label">Menu</p>
      <ul class="menu-list">
        <a routerLink="/products" routerLinkActive="router-link-active">
          <span>Products</span>
        </a>
        <a routerLink="/about" routerLinkActive="router-link-active">
          <span>About</span>
        </a>
      </ul>
    </nav>
    <nav class="menu auth">
      <p class="menu-label">Auth</p>
      <div class="menu-list auth">
        <ng-container *ngIf="!userInfo; else logout">
          <ng-container *ngFor="let provider of providers">
            <a href="/.auth/login/{{ provider }}?post_login_redirect_uri={{ redirect }}">{{ provider }}</a>
          </ng-container>
        </ng-container>
        <ng-template #logout>
          <a href="/.auth/logout?post_logout_redirect_uri={{ redirect }}">Logout</a>
        </ng-template>
      </div>
    </nav>
    <div class="user" *ngIf="userInfo">
      <p>Welcome</p>
      <p>{{ userInfo?.userDetails }}</p>
      <p>{{ userInfo?.identityProvider }}</p>
    </div>
  `,
})
export class NavComponent implements OnInit {
  providers = ['x', 'github', 'aad'];
  redirect = window.location.pathname;
  userInfo: UserInfo;

  async ngOnInit() {
    this.userInfo = await this.getUserInfo();
  }

  async getUserInfo() {
    try {
      const response = await fetch('/.auth/me');
      const payload = await response.json();
      const { clientPrincipal } = payload;
      return clientPrincipal;
    } catch (error) {
      console.error('No profile could be found');
      return undefined;
    }
  }
}

Edite el archivo react-app/src/components/NavBar.js y agregue este código en la parte inferior de la plantilla JSX después de la última etiqueta </nav> de cierre, para mostrar el estado de inicio de sesión.

{
  userInfo && (
    <div>
      <div className="user">
        <p>Welcome</p>
        <p>{userInfo && userInfo.userDetails}</p>
        <p>{userInfo && userInfo.identityProvider}</p>
      </div>
    </div>
  )
}

Nota:

La propiedad userDetails puede ser un nombre de usuario o una dirección de correo electrónico, en función de la identidad proporcionada para iniciar sesión.

Una vez completado, el archivo debería tener el siguiente aspecto:

import React, { useState, useEffect } from 'react';
import { NavLink } from 'react-router-dom';

const NavBar = (props) => {
  const providers = ['x', 'github', 'aad'];
  const redirect = window.location.pathname;
  const [userInfo, setUserInfo] = useState();

  useEffect(() => {
    (async () => {
      setUserInfo(await getUserInfo());
    })();
  }, []);

  async function getUserInfo() {
    try {
      const response = await fetch('/.auth/me');
      const payload = await response.json();
      const { clientPrincipal } = payload;
      return clientPrincipal;
    } catch (error) {
      console.error('No profile could be found');
      return undefined;
    }
  }

  return (
    <div className="column is-2">
      <nav className="menu">
        <p className="menu-label">Menu</p>
        <ul className="menu-list">
          <NavLink to="/products" activeClassName="active-link">
            Products
          </NavLink>
          <NavLink to="/about" activeClassName="active-link">
            About
          </NavLink>
        </ul>
        {props.children}
      </nav>
      <nav className="menu auth">
        <p className="menu-label">Auth</p>
        <div className="menu-list auth">
          {!userInfo &&
            providers.map((provider) => (
              <a key={provider} href={`/.auth/login/${provider}?post_login_redirect_uri=${redirect}`}>
                {provider}
              </a>
            ))}
          {userInfo && <a href={`/.auth/logout?post_logout_redirect_uri=${redirect}`}>Logout</a>}
        </div>
      </nav>
      {userInfo && (
        <div>
          <div className="user">
            <p>Welcome</p>
            <p>{userInfo && userInfo.userDetails}</p>
            <p>{userInfo && userInfo.identityProvider}</p>
          </div>
        </div>
      )}
    </div>
  );
};

export default NavBar;

Edite el archivo svelte-app/src/components/NavBar.svelte y agregue este código en la parte inferior de la plantilla después de la última etiqueta </nav> de cierre, para mostrar el estado de inicio de sesión.

{#if userInfo}
<div class="user">
  <p>Welcome</p>
  <p>{userInfo && userInfo.userDetails}</p>
  <p>{userInfo && userInfo.identityProvider}</p>
</div>
{/if}

Nota:

La propiedad userDetails puede ser un nombre de usuario o una dirección de correo electrónico, en función de la identidad proporcionada para iniciar sesión.

Una vez completado, el archivo debería tener el siguiente aspecto:

<script>
  import { onMount } from 'svelte';
  import { Link } from 'svelte-routing';

  const providers = ['x', 'github', 'aad'];
  const redirect = window.location.pathname;
  let userInfo = undefined;

  onMount(async () => (userInfo = await getUserInfo()));

  async function getUserInfo() {
    try {
      const response = await fetch('/.auth/me');
      const payload = await response.json();
      const { clientPrincipal } = payload;
      return clientPrincipal;
    } catch (error) {
      console.error('No profile could be found');
      return undefined;
    }
  }

  function getProps({ href, isPartiallyCurrent, isCurrent }) {
    const isActive = href === '/' ? isCurrent : isPartiallyCurrent || isCurrent;

    // The object returned here is spread on the anchor element's attributes
    if (isActive) {
      return { class: 'router-link-active' };
    }
    return {};
  }
</script>

<div class="column is-2">
  <nav class="menu">
    <p class="menu-label">Menu</p>
    <ul class="menu-list">
      <Link to="/products" {getProps}>Products</Link>
      <Link to="/about" {getProps}>About</Link>
    </ul>
  </nav>
  <nav class="menu auth">
    <p class="menu-label">Auth</p>
    <div class="menu-list auth">
      {#if !userInfo}
        {#each providers as provider (provider)}
          <a href={`/.auth/login/${provider}?post_login_redirect_uri=${redirect}`}>
            {provider}
          </a>
        {/each}
      {/if}
      {#if userInfo}
        <a href={`/.auth/logout?post_logout_redirect_uri=${redirect}`}>
          Logout
        </a>
      {/if}
    </div>
  </nav>
  {#if userInfo}
    <div class="user">
      <p>Welcome</p>
      <p>{userInfo && userInfo.userDetails}</p>
      <p>{userInfo && userInfo.identityProvider}</p>
    </div>
  {/if}
</div>

Edite el archivo vue-app/src/components/nav-bar.vue y agregue este código en la parte inferior de la plantilla después de la última etiqueta </nav> de cierre, para mostrar el estado de inicio de sesión:

<div class="user" v-if="userInfo">
  <p>Welcome</p>
  <p>{{ userInfo.userDetails }}</p>
  <p>{{ userInfo.identityProvider }}</p>
</div>

Nota:

La propiedad userDetails puede ser un nombre de usuario o una dirección de correo electrónico, en función de la identidad proporcionada para iniciar sesión.

Una vez completado, el archivo debería tener el siguiente aspecto:

<script>
  export default {
    name: 'NavBar',
    data() {
      return {
        userInfo: {
          type: Object,
          default() {},
        },
        providers: ['x', 'github', 'aad'],
        redirect: window.location.pathname,
      };
    },
    methods: {
      async getUserInfo() {
        try {
          const response = await fetch('/.auth/me');
          const payload = await response.json();
          const { clientPrincipal } = payload;
          return clientPrincipal;
        } catch (error) {
          console.error('No profile could be found');
          return undefined;
        }
      },
    },
    async created() {
      this.userInfo = await this.getUserInfo();
    },
  };
</script>

<template>
  <div column is-2>
    <nav class="menu">
      <p class="menu-label">Menu</p>
      <ul class="menu-list">
        <router-link to="/products">Products</router-link>
        <router-link to="/about">About</router-link>
      </ul>
    </nav>
    <nav class="menu auth">
      <p class="menu-label">Auth</p>
      <div class="menu-list auth">
        <template v-if="!userInfo">
          <template v-for="provider in providers">
            <a :key="provider" :href="`/.auth/login/${provider}?post_login_redirect_uri=${redirect}`">{{ provider }}</a>
          </template>
        </template>
        <a v-if="userInfo" :href="`/.auth/logout?post_logout_redirect_uri=${redirect}`">Logout</a>
      </div>
    </nav>
    <div class="user" v-if="userInfo">
      <p>Welcome</p>
      <p>{{ userInfo.userDetails }}</p>
      <p>{{ userInfo.identityProvider }}</p>
    </div>
  </div>
</template>

Comprobación de la autenticación de forma local

Todo está en orden. El último paso consiste en probar si todo funciona según lo previsto.

  1. En la aplicación web, seleccione uno de los proveedores de identidades para iniciar sesión.

  2. Se le redirigirá a esta página:

    Captura de pantalla que muestra una pantalla de autenticación falsa de la CLI de SWA

    Esta es una pantalla de autenticación falsa proporcionada por la CLI de SWA que le permite probar la autenticación de forma local al proporcionar los detalles del usuario.

  3. Escriba mslearn como nombre de usuario y 1234 como identificador de usuario.

  4. Seleccione Login (Iniciar sesión).

    Después de iniciar sesión, se le redirigirá a la página anterior. Puede ver que los botones de inicio de sesión se han reemplazado por un botón de cierre de sesión. También puede ver el nombre de usuario y el proveedor seleccionado debajo del botón de cierre de sesión.

    Ahora que ha comprobado que todo funciona según lo previsto en el entorno local, es el momento de implementar los cambios.

  5. Puede detener la aplicación y la API en ejecución si presiona Ctrl+C en ambos terminales.

Implementación de los cambios

  1. En Visual Studio Code, presione F1 para abrir la paleta de comandos.

  2. Escriba y seleccione Git: Confirmar todo.

  3. Escriba Add authentication como mensaje de confirmación y presione Entrar.

  4. Presione F1 para abrir la paleta de comandos.

  5. Escriba y seleccione Git: Insertar y luego presione Entrar.

Después de enviar los cambios (“push”), espere a que se ejecute el proceso de compilación e implementación. Los cambios deben estar visibles en la aplicación implementada después de realizar la acción.

Pasos siguientes

La aplicación admite ahora la autenticación de usuarios. El paso siguiente consiste en restringir algunas partes de la aplicación para usuarios no autenticados.