Ejercicio: Adición de la autenticación de usuario
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:
- Instalar la CLI de Static Web Apps para el desarrollo local
- Ejecutar la aplicación y la API de forma local con emulación de autenticación local
- Agregar botones de inicio de sesión para varios proveedores de autenticación
- Agregar un botón de cierre de sesión si el usuario ha iniciado sesión
- Mostrar el estado de inicio de sesión del usuario
- Probar el flujo de trabajo de autenticación de forma local
- 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.
Abra un terminal en el equipo.
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.
Abra el proyecto en Visual Studio Code.
En Visual Studio Code, presione F1 para abrir la paleta de comandos.
Escriba y seleccione Terminal: Crear nuevo terminal integrado.
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
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.
En Visual Studio Code, presione F1 para abrir la paleta de comandos.
Escriba y seleccione Terminal: Crear nuevo terminal integrado.
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
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
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.
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[]; }
Edite el archivo
angular-app/src/app/core/components/nav.component.ts
y agregue el siguiente método a la claseNavComponent
.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; } }
Cree una propiedad de clase
userInfo
y almacene el resultado de la función asincrónicagetUserInfo()
cuando se inicialice el componente. Implemente la interfazOnInit
y actualice las instrucciones import para importarOnInit
yUserInfo
. 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(); } // ... }
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 ( // ...
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; } }
Edite el archivo
vue-app/src/components/nav-bar.vue
y agregueuserInfo
al objeto de datos.data() { return { userInfo: { type: Object, default() {}, }, }; },
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; } }, },
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.
Edite el archivo
angular-app/src/app/core/components/nav.component.ts
para agregar una lista de proveedores a la claseNavComponent
.providers = ['x', 'github', 'aad'];
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;
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.
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'];
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;
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.
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'];
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;
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.
Edite el archivo
vue-app/src/components/nav-bar.vue
y agregue una lista de proveedores al objeto de datos.providers: ['x', 'github', 'aad'],
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,
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.
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.
En la aplicación web, seleccione uno de los proveedores de identidades para iniciar sesión.
Se le redirigirá a esta página:
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.
Escriba
mslearn
como nombre de usuario y1234
como identificador de usuario.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.
Puede detener la aplicación y la API en ejecución si presiona Ctrl+C en ambos terminales.
Implementación de los cambios
En Visual Studio Code, presione F1 para abrir la paleta de comandos.
Escriba y seleccione Git: Confirmar todo.
Escriba
Add authentication
como mensaje de confirmación y presione Entrar.Presione F1 para abrir la paleta de comandos.
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.