Övning – Lägga till användarautentisering
Webbappen för inköpslistan behöver användarautentisering. I den här övningen implementerar du inloggning och utloggning i appen och visar den aktuella användarinloggningsstatusen.
I den här övningen ska du:
- Installera Static Web Apps CLI för lokal utveckling.
- Kör appen och API:et lokalt med lokal autentiseringsemulering.
- Lägg till inloggningsknappar för flera autentiseringsprovidrar.
- Lägg till en utloggningsknapp om användaren är inloggad.
- Visa inloggningsstatus för användaren.
- Testa arbetsflödet för autentisering lokalt.
- Distribuera den uppdaterade appen.
Förbereda för lokal utveckling
Static Web Apps CLI, även kallat SWA CLI, är ett lokalt utvecklingsverktyg som gör att du kan köra webbappen och API:et lokalt och emulera autentiserings- och auktoriseringsservrar.
Öppna en terminal på datorn.
Installera SWA CLI som kör följande kommando.
npm install -g @azure/static-web-apps-cli
Köra appen lokalt
Kör nu appen och API:et lokalt med en utvecklingsserver. På så sätt kan du se och testa dina ändringar, precis som du gör dem i koden.
Öppna projektet i Visual Studio Code.
Öppna kommandopaletten i Visual Studio Code genom att trycka på F1.
Ange och välj Terminal: Skapa ny integrerad terminal.
Gå till mappen för det klientramverk som du föredrar enligt följande:
cd angular-app
cd react-app
cd svelte-app
cd vue-app
Kör klientprogrammet på klientsidan med hjälp av en utvecklingsserver.
npm start
npm start
npm run dev
npm run serve
Låt den här servern vara igång i bakgrunden. Kör nu API:et och autentiseringsserveremulatorn med hjälp av SWA CLI.
Öppna kommandopaletten i Visual Studio Code genom att trycka på F1.
Ange och välj Terminal: Skapa ny integrerad terminal.
Kör SWA CLI genom att köra följande kommando:
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
Bläddra till
http://localhost:4280
.
Den sista porten som används av SWA CLI skiljer sig från den du har sett tidigare, eftersom den använder en omvänd proxy för att vidarebefordra begäranden till de tre olika komponenterna:
- Din ramverksutvecklingsserver
- Emulatorn för autentisering och auktorisering
- API:et som körs av Functions-körningen
Låt programmet fortsätta att köras medan du ändrar koden.
Hämta användarens inloggningsstatus
Först måste du komma åt användarens inloggningsstatus genom att göra en fråga till /.auth/me
i klienten.
Skapa filen
angular-app/src/app/core/models/user-info.ts
och lägg till följande kod för att representera gränssnittet för användarinformationen.export interface UserInfo { identityProvider: string; userId: string; userDetails: string; userRoles: string[]; }
Redigera filen
angular-app/src/app/core/components/nav.component.ts
och lägg till följande metod iNavComponent
klassen.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; } }
Skapa en ny klassegenskap
userInfo
och lagra resultatet av funktionengetUserInfo()
async när komponenten initieras.OnInit
Implementera gränssnittet och uppdatera importinstruktionerna för importOnInit
ochUserInfo
. Den här koden hämtar användarinformationen när komponenten initieras.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(); } // ... }
Redigera filen
react-app/src/components/NavBar.js
och lägg till följande kod överst i funktionen. Den här koden hämtar användarinformationen när komponenten läses in och lagrar den i tillståndet.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 ( // ...
Redigera filen
svelte-app/src/components/NavBar.svelte
och lägg till följande kod i skriptavsnittet. Den här koden hämtar användarinformationen när komponenten läses in.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; } }
Redigera filen
vue-app/src/components/nav-bar.vue
och lägg tilluserInfo
i dataobjektet.data() { return { userInfo: { type: Object, default() {}, }, }; },
Lägg till metoden i
getUserInfo()
avsnittet metoder .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; } }, },
created
Lägg till livscykelkroken i komponenten.async created() { this.userInfo = await this.getUserInfo(); },
När komponenten skapas hämtas användarinformationen automatiskt.
Lägg till inloggnings- och utloggningsknappar
Användarinformationen blir undefined
om de inte är inloggade, så ändringarna visas inte för tillfället. Det är dags att lägga till inloggningsknappar för de olika leverantörerna.
Redigera filen
angular-app/src/app/core/components/nav.component.ts
för att lägga till en lista över providrar iNavComponent
klassen.providers = ['x', 'github', 'aad'];
Lägg till följande
redirect
egenskap för att avbilda den aktuella URL:en för omdirigeringen efter inloggningen.redirect = window.location.pathname;
Lägg till följande kod i mallen efter det första
</nav>
elementet för att visa inloggnings- och utloggningsknapparna.<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>
Om användaren inte är inloggad visar du inloggningsknappen för varje provider. Varje knapp länkar till
/.auth/login/<AUTH_PROVIDER>
och anger omdirigerings-URL:en till den aktuella sidan.Om användaren redan är inloggad visas annars en utloggningsknapp som länkar till
/.auth/logout
och anger även omdirigerings-URL:en till den aktuella sidan.
Nu bör du se den här webbsidan i webbläsaren.
Redigera filen
react-app/src/components/NavBar.js
för att lägga till en lista över leverantörer överst i funktionen.const providers = ['x', 'github', 'aad'];
Lägg till följande
redirect
variabel under den första variabeln för att avbilda den aktuella URL:en för omdirigeringen efter inloggningen.const redirect = window.location.pathname;
Lägg till följande kod i JSX-mallen efter det första
</nav>
elementet för att visa inloggnings- och utloggningsknapparna.<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>
Om användaren inte är inloggad visar du inloggningsknappen för varje provider. Varje knapp länkar till
/.auth/login/<AUTH_PROVIDER>
och anger omdirigerings-URL:en till den aktuella sidan.Om användaren redan är inloggad visar du annars en utloggningsknapp som länkar till
/.auth/logout
och anger även omdirigerings-URL:en till den aktuella sidan.
Nu bör du se den här webbsidan i webbläsaren.
Redigera filen
svelte-app/src/components/NavBar.svelte
för att lägga till en lista över leverantörer överst i skriptet.const providers = ['x', 'github', 'aad'];
Lägg till följande
redirect
variabel under den första variabeln för att avbilda den aktuella URL:en för omdirigeringen efter inloggningen.const redirect = window.location.pathname;
Lägg till följande kod i mallen efter det första
</nav>
elementet för att visa inloggnings- och utloggningsknapparna.<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>
Om användaren inte är inloggad visar du inloggningsknappen för varje provider. Varje knapp länkar till
/.auth/login/<AUTH_PROVIDER>
och anger omdirigerings-URL:en till den aktuella sidan.Om användaren redan är inloggad visar du annars en utloggningsknapp som länkar till
/.auth/logout
och anger även omdirigerings-URL:en till den aktuella sidan.
Nu bör du se den här webbsidan i webbläsaren.
Redigera filen
vue-app/src/components/nav-bar.vue
och lägg till en lista över providers i dataobjektet.providers: ['x', 'github', 'aad'],
Lägg till följande
redirect
egenskap för att avbilda den aktuella URL:en för omdirigeringen efter inloggningen.redirect: window.location.pathname,
Lägg till följande kod i mallen efter det första
</nav>
elementet för att visa inloggnings- och utloggningsknapparna.<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>
Om användaren inte är inloggad visar du inloggningsknappen för varje provider. Varje knapp länkar till
/.auth/login/<AUTH_PROVIDER>
och anger omdirigerings-URL:en till den aktuella sidan.Om användaren redan är inloggad visar du annars en utloggningsknapp som länkar till
/.auth/logout
och anger även omdirigerings-URL:en till den aktuella sidan.
Nu bör du se den här webbsidan i webbläsaren.
Visa användarens inloggningsstatus
Innan du testar autentiseringsarbetsflödet ska vi visa användarinformation om den inloggade användaren.
Redigera filen angular-app/src/app/core/components/nav.component.ts
och lägg till den här koden längst ned i mallen efter den slutliga avslutande </nav>
taggen.
<div class="user" *ngIf="userInfo">
<p>Welcome</p>
<p>{{ userInfo?.userDetails }}</p>
<p>{{ userInfo?.identityProvider }}</p>
</div>
Kommentar
Egenskapen userDetails
kan vara antingen ett användarnamn eller en e-postadress, beroende på vilken identitet som angavs för att logga in.
Den färdiga filen bör nu se ut så här:
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;
}
}
}
Redigera filen react-app/src/components/NavBar.js
och lägg till den här koden längst ned i JSX-mallen efter den sista avslutande </nav>
taggen för att visa inloggningsstatusen.
{
userInfo && (
<div>
<div className="user">
<p>Welcome</p>
<p>{userInfo && userInfo.userDetails}</p>
<p>{userInfo && userInfo.identityProvider}</p>
</div>
</div>
)
}
Kommentar
Egenskapen userDetails
kan vara antingen ett användarnamn eller en e-postadress, beroende på vilken identitet som angavs för att logga in.
Den färdiga filen bör nu se ut så här:
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;
Redigera filen svelte-app/src/components/NavBar.svelte
och lägg till den här koden längst ned i mallen efter den sista avslutande </nav>
taggen för att visa inloggningsstatusen.
{#if userInfo}
<div class="user">
<p>Welcome</p>
<p>{userInfo && userInfo.userDetails}</p>
<p>{userInfo && userInfo.identityProvider}</p>
</div>
{/if}
Kommentar
Egenskapen userDetails
kan vara antingen ett användarnamn eller en e-postadress, beroende på vilken identitet som angavs för att logga in.
Den färdiga filen bör nu se ut så här:
<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>
Redigera filen vue-app/src/components/nav-bar.vue
och lägg till den här koden längst ned i mallen efter den sista avslutande </nav>
taggen för att visa inloggningsstatusen:
<div class="user" v-if="userInfo">
<p>Welcome</p>
<p>{{ userInfo.userDetails }}</p>
<p>{{ userInfo.identityProvider }}</p>
</div>
Kommentar
Egenskapen userDetails
kan vara antingen ett användarnamn eller en e-postadress, beroende på vilken identitet som angavs för att logga in.
Den färdiga filen bör nu se ut så här:
<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>
Testa autentisering lokalt
Allt är nu på plats. Det sista steget är att testa om allt fungerar som förväntat.
I webbappen väljer du en av identitetsprovidrar som ska loggas in.
Du omdirigeras till den här sidan:
Det här är en skärm för falsk autentisering som tillhandahålls av SWA CLI, så att du kan testa autentisering lokalt genom att ange användarinformation.
Ange
mslearn
som användarnamn och1234
som användar-ID.Välj Logga in.
Efter inloggningen omdirigeras du till föregående sida. Du kan se att inloggningsknapparna har ersatts av en utloggningsknapp. Du kan också se ditt användarnamn och den valda providern under utloggningsknappen.
Nu när du har kontrollerat att allt fungerar som förväntat lokalt är det dags att distribuera ändringarna.
Du kan stoppa appen och API:et som körs genom att trycka på Ctrl-C i båda terminalerna.
Distribuera dina ändringar
Öppna kommandopaletten i Visual Studio Code genom att trycka på F1.
Ange och välj Git: Checka in alla.
Ange
Add authentication
som incheckningsmeddelande och tryck på Retur.Öppna kommandopaletten genom att trycka på F1.
Ange och välj Git: Push och tryck på Retur.
När du har push-överfört ändringarna väntar du på att bygg- och distributionsprocessen ska köras. Ändringarna ska vara synliga i din distribuerade app efter det.
Nästa steg
Appen stöder nu användarautentisering och nästa steg är att begränsa vissa delar av appen till oautentiserade användare.