Exercício – Adicionar autenticação de usuário
Seu aplicativo Web de lista de compras precisa de uma autenticação de usuário. Neste exercício, você implementará o logon e o logoff em seu aplicativo e exibirá o status de logon atual do usuário.
Neste exercício, você vai concluir as seguintes etapas:
- Instalar a CLI de Aplicativos Web Estáticos para desenvolvimento local.
- Executar o aplicativo e a API localmente com a emulação de autenticação local.
- Adicionar botões de logon para vários provedores de autenticação.
- Adicionar um botão de logoff se o usuário estiver conectado.
- Exibir o status de logon do usuário.
- Testar o fluxo de trabalho da autenticação localmente.
- Implantar o aplicativo atualizado.
Preparar-se para o desenvolvimento local
A CLI de Aplicativos Web Estáticos, também conhecida como CLI do SWA, é uma ferramenta de desenvolvimento local que permite executar seu aplicativo Web e a API localmente e emular servidores de autenticação e autorização.
Abra um terminal no seu computador.
Instale a CLI do SWA executando o comando a seguir.
npm install -g @azure/static-web-apps-cli
Executar o aplicativo localmente
Agora, execute o aplicativo e a API localmente com um servidor de desenvolvimento. Dessa forma, você poderá ver e testar suas alterações à medida que implementá-las no código.
Abra o projeto no Visual Studio Code.
No Visual Studio Code, abra a paleta de comandos pressionando F1.
Insira e selecione Terminal: criar terminal integrado.
Vá para a pasta da sua estrutura de front-end preferida, conforme o seguinte:
cd angular-app
cd react-app
cd svelte-app
cd vue-app
Execute o aplicativo cliente de front-end usando um servidor de desenvolvimento.
npm start
npm start
npm run dev
npm run serve
Deixe esse servidor em execução em segundo plano. Agora, execute a API e o emulador do servidor de autenticação usando a CLI do SWA.
No Visual Studio Code, abra a paleta de comandos pressionando F1.
Insira e selecione Terminal: criar terminal integrado.
Execute a CLI do SWA usando 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 até
http://localhost:4280
.
A porta final usada pela CLI do SWA é diferente da que você já viu antes, porque ela usa um proxy reverso para encaminhar solicitações aos três componentes diferentes:
- Seu servidor de desenvolvimento de estrutura
- O emulador de autenticação e autorização
- A API hospedada pelo runtime do Functions
Deixe o aplicativo em execução enquanto você modifica o código.
Obter o status de logon do usuário
Primeiro, você precisa acessar o status de logon do usuário fazendo uma consulta em /.auth/me
no 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 método a seguir à classeNavComponent
.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 propriedade de classe
userInfo
e armazene o resultado da função assíncronagetUserInfo()
quando o componente for inicializado. Implemente a interfaceOnInit
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 código a seguir ao início 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 código a seguir à seção do 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 método
getUserInfo()
à seção 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; } }, },
Adicione o gancho do ciclo de vida
created
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 logon e logoff
As informações do usuário serão undefined
se ele não estiver conectado. Portanto, as alterações não ficarão visíveis por enquanto. É hora de adicionar botões de logon para os diferentes provedores.
Edite o arquivo
angular-app/src/app/core/components/nav.component.ts
para adicionar uma lista de provedores à classeNavComponent
.providers = ['x', 'github', 'aad'];
Adicione a propriedade
redirect
a seguir a fim de capturar a URL atual para o redirecionamento após o logon.redirect = window.location.pathname;
Adicione o código a seguir ao modelo após o primeiro elemento
</nav>
para exibir os botões de logon e de logoff.<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 logon de cada provedor. Cada botão vincula a
/.auth/login/<AUTH_PROVIDER>
e define a URL de redirecionamento para a página atual.Caso contrário, se o usuário já estiver conectado, será exibido um botão de logoff que está vinculado a
/.auth/logout
e que define a URL de redirecionamento como a página atual.
Agora esta página da Web deverá ser exibida no seu navegador.
Edite o arquivo
react-app/src/components/NavBar.js
para adicionar uma lista de provedores à parte superior da função.const providers = ['x', 'github', 'aad'];
Adicione a variável
redirect
a seguir abaixo da primeira variável a fim de capturar a URL atual para o redirecionamento após o logon.const redirect = window.location.pathname;
Adicione o código a seguir ao modelo JSX após o primeiro elemento
</nav>
para exibir os botões de logon e de logoff.<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 logon de cada provedor. Cada botão vincula a
/.auth/login/<AUTH_PROVIDER>
e define a URL de redirecionamento para a página atual.Caso contrário, se o usuário já estiver conectado, você exibirá um botão de logoff que está vinculado a
/.auth/logout
e que define a URL de redirecionamento como a página atual.
Agora esta página da Web deverá ser exibida no seu navegador.
Edite o arquivo
svelte-app/src/components/NavBar.svelte
para adicionar uma lista de provedores à parte superior do script.const providers = ['x', 'github', 'aad'];
Adicione a variável
redirect
a seguir abaixo da primeira variável a fim de capturar a URL atual para o redirecionamento após o logon.const redirect = window.location.pathname;
Adicione o código a seguir ao modelo após o primeiro elemento
</nav>
para exibir os botões de logon e de logoff.<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 logon de cada provedor. Cada botão vincula a
/.auth/login/<AUTH_PROVIDER>
e define a URL de redirecionamento para a página atual.Caso contrário, se o usuário já estiver conectado, você exibirá um botão de logoff que está vinculado a
/.auth/logout
e que define a URL de redirecionamento como a página atual.
Agora esta página da Web deverá ser exibida no 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
redirect
a seguir a fim de capturar a URL atual para o redirecionamento após o logon.redirect: window.location.pathname,
Adicione o código a seguir ao modelo após o primeiro elemento
</nav>
para exibir os botões de logon e de logoff.<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 logon de cada provedor. Cada botão vincula a
/.auth/login/<AUTH_PROVIDER>
e define a URL de redirecionamento para a página atual.Caso contrário, se o usuário já estiver conectado, você exibirá um botão de logoff que está vinculado a
/.auth/logout
e que define a URL de redirecionamento como a página atual.
Agora esta página da Web deverá ser exibida no seu navegador.
Exibir o status de logon 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 este código à parte inferior do modelo após a marca </nav>
de fechamento final.
<div class="user" *ngIf="userInfo">
<p>Welcome</p>
<p>{{ userInfo?.userDetails }}</p>
<p>{{ userInfo?.identityProvider }}</p>
</div>
Observação
A propriedade userDetails
pode ser um nome de usuário ou endereço de email, dependendo da identidade fornecida usada para fazer logon.
O seu arquivo completo deverá estar como o seguinte:
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 este código à parte inferior do modelo JSX após a marca </nav>
de fechamento final para exibir o status de logon.
{
userInfo && (
<div>
<div className="user">
<p>Welcome</p>
<p>{userInfo && userInfo.userDetails}</p>
<p>{userInfo && userInfo.identityProvider}</p>
</div>
</div>
)
}
Observação
A propriedade userDetails
pode ser um nome de usuário ou endereço de email, dependendo da identidade fornecida usada para fazer logon.
O seu arquivo completo deverá estar como o seguinte:
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 este código à parte inferior do modelo após a marca </nav>
de fechamento final para exibir o status de logon.
{#if userInfo}
<div class="user">
<p>Welcome</p>
<p>{userInfo && userInfo.userDetails}</p>
<p>{userInfo && userInfo.identityProvider}</p>
</div>
{/if}
Observação
A propriedade userDetails
pode ser um nome de usuário ou endereço de email, dependendo da identidade fornecida usada para fazer logon.
O seu arquivo completo deverá estar como o seguinte:
<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 marca </nav>
de fechamento final para exibir o status de logon:
<div class="user" v-if="userInfo">
<p>Welcome</p>
<p>{{ userInfo.userDetails }}</p>
<p>{{ userInfo.identityProvider }}</p>
</div>
Observação
A propriedade userDetails
pode ser um nome de usuário ou endereço de email, dependendo da identidade fornecida usada para fazer logon.
O seu arquivo completo deverá estar como o seguinte:
<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 em seu devido lugar. A etapa final é testar se tudo está funcionando conforme o esperado.
Em seu aplicativo Web, selecione um dos provedores de identidade para fazer logon.
Você será redirecionado para esta página:
Essa é uma tela de autenticação falsa fornecida pela CLI do SWA, permitindo que você teste a autenticação localmente simulando o fornecimento de detalhes de usuário.
Insira
mslearn
como nome de usuário e1234
para a ID de usuário.Selecione Fazer logon.
Após o logon, você será redirecionado para a página anterior. Você pode ver que os botões de logon foram substituídos por um botão de logoff. Você também pode ver seu nome de usuário e o provedor selecionado abaixo do botão de logoff.
Agora que você verificou se tudo funciona conforme o esperado localmente, é hora de implantar suas alterações.
Você pode interromper o aplicativo e a API em execução pressionando Ctrl-C em ambos os terminais.
Implantar suas alterações
No Visual Studio Code, abra a paleta de comandos pressionando F1.
Insira e selecione Git: confirmar tudo.
Insira
Add authentication
como a mensagem de confirmação e pressione Enter.Abra a paleta de comandos pressionando F1.
Insira 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 no aplicativo implantado depois disso.
Próximas etapas
Seu aplicativo agora dá suporte à autenticação de usuário, e a próxima etapa é restringir algumas partes do aplicativo a usuários não autenticados.