Exercício - Adicionar autenticação de usuário
Seu aplicativo Web de lista de compras precisa de autenticação do usuário. Neste exercício, você implementará o login e o logout em seu aplicativo e exibirá o status atual de login do usuário.
Neste exercício, irá seguir os seguintes passos:
- Instale a CLI de aplicativos Web estáticos para desenvolvimento local.
- Execute o aplicativo e a API localmente com emulação de autenticação local.
- Adicione botões de login para vários provedores de autenticação.
- Adicione um botão de logout se o usuário estiver conectado.
- Exiba o status de login do usuário.
- Teste o fluxo de trabalho de autenticação localmente.
- Implante o aplicativo atualizado.
Preparar o desenvolvimento local
A CLI de aplicativos Web estáticos, também conhecida como CLI SWA, é uma ferramenta de desenvolvimento local que permite executar seu aplicativo Web e API localmente e emular servidores de autenticação e autorização.
Abra um terminal no computador.
Instale a CLI SWA executando o seguinte comando.
npm install -g @azure/static-web-apps-cli
Executar a aplicação localmente
Agora execute o aplicativo e a API localmente com um servidor de desenvolvimento. Desta forma, poderá ver e testar as suas alterações, tal como as faz no código.
Abra o projeto no Visual Studio Code.
No Visual Studio Code, abra a paleta de comandos pressionando F1.
Entre e selecione Terminal: Criar novo terminal integrado.
Vá para a pasta da sua estrutura de front-end preferida, da seguinte maneira:
cd angular-app
cd react-app
cd svelte-app
cd vue-app
Execute o aplicativo cliente front-end usando um servidor de desenvolvimento.
npm start
npm start
npm run dev
npm run serve
Deixe este servidor em execução em segundo plano. Agora execute a API e o emulador de servidor de autenticação usando a CLI SWA.
No Visual Studio Code, abra a paleta de comandos pressionando F1.
Entre e selecione Terminal: Criar novo terminal integrado.
Execute a CLI SWA executando o seguinte 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
Navegue para
http://localhost:4280
.
A porta final usada pela CLI SWA é diferente da que você viu antes, porque usa um proxy reverso para encaminhar solicitações para os três componentes diferentes:
- Seu servidor de desenvolvimento de estrutura
- O emulador de autenticação e autorização
- A API hospedada pelo tempo de execução do Functions
Deixe o aplicativo permanecer em execução enquanto você modifica o código.
Obter o status de login do usuário
Primeiro, você precisa acessar o status de login do usuário fazendo uma consulta no /.auth/me
cliente.
Crie o arquivo
angular-app/src/app/core/models/user-info.ts
e adicione o código a seguir para representar a interface para as informações do usuário.export interface UserInfo { identityProvider: string; userId: string; userDetails: string; userRoles: string[]; }
Edite o arquivo
angular-app/src/app/core/components/nav.component.ts
e adicione o seguinte método naNavComponent
classe.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; } }
Crie uma nova propriedade
userInfo
de classe e armazene o resultado da funçãogetUserInfo()
assíncrona quando o componente for inicializado. Implemente aOnInit
interface e atualize as instruções de importação para importarOnInit
eUserInfo
. Esse código busca as informações do usuário quando o componente é inicializado.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 o arquivo
react-app/src/components/NavBar.js
e adicione o seguinte código na parte superior da função. Esse código busca as informações do usuário quando o componente é carregado e as armazena no 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 o arquivo
svelte-app/src/components/NavBar.svelte
e adicione o seguinte código na seção de script. Esse código busca as informações do usuário quando o componente é carregado.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 o arquivo
vue-app/src/components/nav-bar.vue
e adicioneuserInfo
ao objeto de dados.data() { return { userInfo: { type: Object, default() {}, }, }; },
Adicione o
getUserInfo()
método à seção de métodos .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; } }, },
Adicione o gancho de
created
ciclo de vida ao componente.async created() { this.userInfo = await this.getUserInfo(); },
Quando o componente é criado, as informações do usuário são buscadas automaticamente.
Adicionar botões de login e logout
As informações do usuário serão undefined
se ele não estiver conectado, portanto, as alterações não serão visíveis por enquanto. É hora de adicionar botões de login para os diferentes provedores.
Edite o arquivo
angular-app/src/app/core/components/nav.component.ts
para adicionar uma lista de provedores naNavComponent
classe.providers = ['x', 'github', 'aad'];
Adicione a propriedade a seguir
redirect
para capturar a URL atual para o redirecionamento de login pós-login.redirect = window.location.pathname;
Adicione o seguinte código ao modelo após o primeiro
</nav>
elemento para exibir os botões de login e logout.<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>
Se o usuário não estiver conectado, você exibirá o botão de login para cada provedor. Cada botão vincula a
/.auth/login/<AUTH_PROVIDER>
, e define o URL de redirecionamento para a página atual.Caso contrário, se o usuário já estiver conectado, um botão de logout exibirá o link para
/.auth/logout
o , e também definirá a URL de redirecionamento para a página atual.
Agora você deve ver esta página da Web em seu navegador.
Edite o arquivo
react-app/src/components/NavBar.js
para adicionar uma lista de provedores na parte superior da função.const providers = ['x', 'github', 'aad'];
Adicione a seguinte
redirect
variável abaixo da primeira variável para capturar a URL atual para o redirecionamento de login pós-login.const redirect = window.location.pathname;
Adicione o seguinte código ao modelo JSX após o primeiro
</nav>
elemento para exibir os botões de login e logout.<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>
Se o usuário não estiver conectado, você exibirá o botão de login para cada provedor. Cada botão vincula a
/.auth/login/<AUTH_PROVIDER>
, e define o URL de redirecionamento para a página atual.Caso contrário, se o usuário já estiver conectado, você exibirá um botão de logout vinculado ao
/.auth/logout
, e também definirá a URL de redirecionamento para a página atual.
Agora você deve ver esta página da Web em seu navegador.
Edite o arquivo
svelte-app/src/components/NavBar.svelte
para adicionar uma lista de provedores na parte superior do script.const providers = ['x', 'github', 'aad'];
Adicione a seguinte
redirect
variável abaixo da primeira variável para capturar a URL atual para o redirecionamento de login pós-login.const redirect = window.location.pathname;
Adicione o seguinte código ao modelo após o primeiro
</nav>
elemento para exibir os botões de login e logout.<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>
Se o usuário não estiver conectado, você exibirá o botão de login para cada provedor. Cada botão vincula a
/.auth/login/<AUTH_PROVIDER>
, e define o URL de redirecionamento para a página atual.Caso contrário, se o usuário já estiver conectado, você exibirá um botão de logout vinculado ao
/.auth/logout
, e também definirá a URL de redirecionamento para a página atual.
Agora você deve ver esta página da Web em seu navegador.
Edite o arquivo
vue-app/src/components/nav-bar.vue
e adicione uma lista de provedores ao objeto de dados.providers: ['x', 'github', 'aad'],
Adicione a propriedade a seguir
redirect
para capturar a URL atual para o redirecionamento de login pós-login.redirect: window.location.pathname,
Adicione o seguinte código ao modelo após o primeiro
</nav>
elemento para exibir os botões de login e logout.<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>
Se o usuário não estiver conectado, você exibirá o botão de login para cada provedor. Cada botão vincula a
/.auth/login/<AUTH_PROVIDER>
, e define o URL de redirecionamento para a página atual.Caso contrário, se o usuário já estiver conectado, você exibirá um botão de logout vinculado ao
/.auth/logout
, e também definirá a URL de redirecionamento para a página atual.
Agora você deve ver esta página da Web em seu navegador.
Exibir o status de login do usuário
Antes de testar o fluxo de trabalho de autenticação, vamos exibir os detalhes do usuário conectado.
Edite o arquivo angular-app/src/app/core/components/nav.component.ts
e adicione esse código à parte inferior do modelo após a tag de fechamento </nav>
final.
<div class="user" *ngIf="userInfo">
<p>Welcome</p>
<p>{{ userInfo?.userDetails }}</p>
<p>{{ userInfo?.identityProvider }}</p>
</div>
Nota
A userDetails
propriedade pode ser um nome de usuário ou endereço de e-mail, dependendo da identidade fornecida usada para fazer login.
Seu arquivo concluído agora deve ter a seguinte aparência:
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 o arquivo react-app/src/components/NavBar.js
e adicione esse código à parte inferior do modelo JSX após a tag de fechamento </nav>
final, para exibir o status de login.
{
userInfo && (
<div>
<div className="user">
<p>Welcome</p>
<p>{userInfo && userInfo.userDetails}</p>
<p>{userInfo && userInfo.identityProvider}</p>
</div>
</div>
)
}
Nota
A userDetails
propriedade pode ser um nome de usuário ou endereço de e-mail, dependendo da identidade fornecida usada para fazer login.
Seu arquivo concluído agora deve ter a seguinte aparência:
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 o arquivo svelte-app/src/components/NavBar.svelte
e adicione esse código à parte inferior do modelo após a tag de fechamento </nav>
final, para exibir o status de login.
{#if userInfo}
<div class="user">
<p>Welcome</p>
<p>{userInfo && userInfo.userDetails}</p>
<p>{userInfo && userInfo.identityProvider}</p>
</div>
{/if}
Nota
A userDetails
propriedade pode ser um nome de usuário ou endereço de e-mail, dependendo da identidade fornecida usada para fazer login.
Seu arquivo concluído agora deve ter a seguinte aparência:
<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 o arquivo vue-app/src/components/nav-bar.vue
e adicione este código à parte inferior do modelo após a tag de fechamento </nav>
final, para exibir o status de login:
<div class="user" v-if="userInfo">
<p>Welcome</p>
<p>{{ userInfo.userDetails }}</p>
<p>{{ userInfo.identityProvider }}</p>
</div>
Nota
A userDetails
propriedade pode ser um nome de usuário ou endereço de e-mail, dependendo da identidade fornecida usada para fazer login.
Seu arquivo concluído agora deve ter a seguinte aparência:
<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>
Testar a autenticação localmente
Agora está tudo pronto. O passo final é testar se tudo está funcionando como esperado.
No seu aplicativo Web, selecione um dos provedores de identidade para fazer login.
Será redirecionado para esta página:
Esta é uma tela de autenticação falsa, fornecida pela SWA CLI, permitindo que você teste a autenticação localmente, fornecendo detalhes do usuário.
Insira
mslearn
como nome de usuário e1234
para o ID de usuário.Selecione Iniciar sessão.
Após o login, você será redirecionado para a página anterior. Você pode ver que os botões de login foram substituídos por um botão de logout. Você também pode ver seu nome de usuário e o provedor selecionado abaixo do botão de logout.
Agora que você verificou se tudo funciona como esperado localmente, é hora de implantar suas alterações.
Você pode parar o aplicativo e a API em execução pressionando Ctrl-C em ambos os terminais.
Implante suas alterações
No Visual Studio Code, abra a paleta de comandos pressionando F1.
Digite e selecione Git: Confirmar tudo.
Digite
Add authentication
como a mensagem de confirmação e pressione Enter.Abra a paleta de comandos ao premir F1.
Digite e selecione Git: Push e pressione Enter.
Depois de enviar as alterações, aguarde até que o processo de compilação e implantação seja executado. As alterações devem ficar visíveis em seu aplicativo implantado depois disso.
Próximos passos
Seu aplicativo agora oferece suporte à autenticação de usuários e sua próxima etapa é restringir algumas partes do aplicativo a usuários não autenticados.