Teams et Bazor WebAssembly - Authentication Token
Introduction
App Registration
- Enregistrez une nouvelle application dans le portail Microsoft Entra:
Identity/Applications/App Registrations - Sélectionnez New Registration et définissez les valeurs suivantes :
- Définissez le Nom comme étant le nom de votre application.
- Choisissez Accounts in any organizational directory (Any Microsoft Entra ID tenant - Multitenant).
- Laissez l’URI de redirection vide.
- Enregistrer.
- Naviguer vers Expose an API.
-
Sélectionnez le lien Add pour générer l’URI de l’Application ID, sous la forme
api://<fully-qualified-domain-name>/<AppID>.L’identifiant
AppIdest la valeur affichée dans Overview / Application (client) ID.Ex:
api://thbkwlr9-my-tunnel.euw.devtunnels.ms/74f3d663-9438-4ea0-9c32-8cf1d000fe9d. - Sélectionnez le bouton Add a scope.
- Saisissez Scope name comme
access_as_user. - Définissez l’option Who can consent? sur
Admins and Users. - Remplissez les champs de configuration des invites de consentement de l’administrateur et de l’utilisateur avec des valeurs appropriées :
- Titre du consentement de l’administrateur :
Teams peut accéder au profil de l'utilisateur. - Description du consentement de l’administrateur :
Permet à Teams d'appeler les API web de l'application en tant qu'utilisateur actuel. - Titre du consentement de l’utilisateur :
Teams peut accéder au profil de l'utilisateur et faire des demandes en son nom. - Description du consentement de l’utilisateur :
Permet à Teams d'appeler les API de cette application avec les mêmes droits que l'utilisateur.
- Titre du consentement de l’administrateur :
- Assurez-vous que le State est défini sur Enabled
- Saisissez Scope name comme
-
La grille Scopes doit automatiquement correspondre à l’URI de l’ID de l’application défini à l’étape précédente:
api://thbkwlr9-my-tunnel.euw.devtunnels.ms/74f3d663-9438-4ea0-9c32-8cf1d000fe9d/access_as_user.⚠️ Contrairement aux autres URLs où il est possible d’ajouter plusieurs domaines différents, cette adresse devra être adaptée avec l’adresse de Production.
- Dans la section Authorized client applications, identifiez les applications clients que vous voulez autoriser. Chacun des identifiants suivants doit être saisi :
1fec8e78-bce4-4aaf-ab1b-5451cc387264(Teams mobile/desktop application)5e3ce6c0-2b1f-4285-8d4b-75ee78787346(Teams web application)
Ces GUIDs sont décrits dans la documentation de Microsoft et correspondent aux identifiants des applications Teams. Ils doivent être utilisés tels quels.

-
- Naviguez jusqu’à API Permissions, et assurez-vous d’ajouter les permissions suivantes :
- Sélectionnez Microsoft Graph et Delegated permissions.
User.Read(activé par défaut)emailoffline_accessopenIdprofil

- Sélectionnez Microsoft Graph et Delegated permissions.
- Naviguer vers Authentication. Les utilisateurs devront donner leur accord la première fois qu’ils utiliseront l’application.
- Définir une URI de redirection :
- Sélectionnez Add a plateform.
- Sélectionnez Single-page application.
- Saisissez l’URI de redirection au format suivant :
https://<fully-qualified-domain-name>/authentication/login-callback. Il s’agit de la page où un flux d’octroi implicite réussi redirigera l’utilisateur.
- Activez les deux cases à cocher de Implicit grant and hybrid flows:
- Access tokens (used for implicit flows)
- ID tokens (used for implicit and hybrid flows)

- Définir une URI de redirection :
Teams Manifest
Une application Teams est définir grâce à son Manifest qui centralise toutes les configurations et fonctionnalités disponibles.
Vous pouvez le créer visuellement via le portail https://dev.teams.microsoft.com
-
Créez un nouvelle application en appuyant sur New app et donnez lui un nom.
- Dans la page Basic information :
- Remplissez tous les champs obligatoires (nom, description, )
-
Encoder le Application (client) ID, identique à l’identifiant
AppIdqui est la valeur affichée dans Overview / Application (client) ID.
- Naviguer vers Configure / App features et cliquez sur Group and channel app:
-
Précisez la Configuration URL qui renseigne la page de configuration de votre projet (
config.html)Ex.
https://thbkwlr9-my-tunnel.euw.devtunnels.ms/config.html - Cochez la case Users can reconfigure the app.
- Selectionner le scope Group chat.
- Laissez les autres champs intacts.

-
- Naviguez vers Single sign-on
-
Complétez l’Application ID URI par l’adresse de l’API exposée dans Azure (ci-dessus). Vérifiez que la valeur
access_as_usern’est PAS présente en fin d’URL.Ex.
api://thbkwlr9-my-tunnel.euw.devtunnels.ms/74f3d663-9438-4ea0-9c32-8cf1d000fe9d

-
- Naviguez vers Domains et vérifiez que votre URL est bien présente.
En appuyant sur le bouton Publish en haut, à droite de l’écran, vous générez un ZIP contenant ce Manifest:
{
"$schema": "https://developer.microsoft.com/en-us/json-schemas/teams/v1.17/MicrosoftTeams.schema.json",
"version": "1.0.0",
"manifestVersion": "1.17",
"id": "699234a3-9171-43ff-a1f1-3a822de11635",
"name": {
"short": "My Application",
"full": "Managing ..."
},
"developer": {
"name": "Denis Voituron",
"mpnId": "",
"websiteUrl": "https://dvoituron.com",
"privacyUrl": "https://dvoituron.com",
"termsOfUseUrl": "https://dvoituron.com"
},
"description": {
"short": "My Application",
"full": "Managing ..."
},
"icons": {
"outline": "outline.png",
"color": "color.png"
},
"accentColor": "#FFFFFF",
"configurableTabs": [
{
"configurationUrl": "https://thbkwlr9-my-tunnel.euw.devtunnels.ms/config.html",
"canUpdateConfiguration": true,
"scopes": [
"groupChat"
]
}
],
"validDomains": [
"thbkwlr9-my-tunnel.euw.devtunnels.ms"
],
"webApplicationInfo": {
"id": "74f3d663-9438-4ea0-9c32-8cf1d000fe9d",
"resource": "api://thbkwlr9-my-tunnel.euw.devtunnels.ms/74f3d663-9438-4ea0-9c32-8cf1d000fe9d"
}
}
⚠️ Si vous avez besoin d’un environnement de Test et un autre de Production, vous devrez créer deux Manifest (ZIP) avec les URLs correctes.
Application Blazor WebAssembly
-
Vous pouvez maintenant créer votre application Blazor WASM et y ajouter les packages NuGet suivants (en précisant la).
<PackageReference Include="Microsoft.AspNetCore.Http" /> <PackageReference Include="Microsoft.Authentication.WebAssembly.Msal" /> <PackageReference Include="System.IdentityModel.Tokens.Jwt" /> -
Adapter votre méthode
Program.Maincomm suit.builder.Services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>(); // Authenticate using Azure MASL builder.Services.AddMsalAuthentication(options => { builder.Configuration.Bind("AzureAd", options.ProviderOptions.Authentication); options.ProviderOptions.DefaultAccessTokenScopes.Add("openid"); options.ProviderOptions.DefaultAccessTokenScopes.Add("offline_access"); }); // Get Token from Teams await builder.Services.AddTeamsAuthenticationAsync(); -
La méthode
AddMsalAuthenticationva chercher ses configuration dans le fichierwwwroot/appsettings.json{ "AzureAd": { "Authority": "https://login.microsoftonline.com/common", "ClientId": "74f3d663-9438-4ea0-9c32-8cf1d000fe9d", // Azure Application Client ID "ValidateAuthority": true } } -
API de redirection
Lors de l’enregistrement de l’application dans Azure, nous avons définit l’adresse de redirection vers
/authentication/login-callback. Nous devons ajouter ce composantAuthentication.razorpour intercepter les appels Azure./* Authentication.razor */ @page "/authentication/{action}" @using Microsoft.AspNetCore.Components.WebAssembly.Authentication <RemoteAuthenticatorView Action="@Action" /> @code{ [Parameter] public string? Action { get; set; } } -
Page d’authentification
Nous avons également besoins des pages de Login et de Redirection suivantes.
/* LoginDisplay.razor */ @using Microsoft.AspNetCore.Components.WebAssembly.Authentication @inject NavigationManager Navigation <AuthorizeView> <Authorized> </Authorized> <NotAuthorized> <a href="authentication/login">Click here to open the log in page.</a> </NotAuthorized> </AuthorizeView> @code { public void BeginLogOut() { Navigation.NavigateToLogout("authentication/logout"); } }/* RedirectToLogin.razor */ @using Microsoft.AspNetCore.Components.WebAssembly.Authentication @inject NavigationManager Navigation @code { protected override void OnInitialized() { Navigation.NavigateToLogin("authentication/login"); } } -
La méthode
AddTeamsAuthenticationAsyncest disponible via cette classe d’extensions qui fait appel à la classexxxqui suit.L’appel à la fonction JavaScript
insideTeamsdemande l’ajout de cette fonction dans le fichierindex.html. Les autres appels des fonctionsmicrosoftTeamsproviennt du scriptMicrosoftTeams.min.jsréférencé également dansindex.html.<script src="https://res.cdn.office.net/teams-js/2.0.0/js/MicrosoftTeams.min.js"></script> <script> function insideTeams() { return window.parent !== window.self; } </script>using Microsoft.AspNetCore.Components.Authorization; using Microsoft.JSInterop; using System.IdentityModel.Tokens.Jwt; public static class AuthenticationStateAuthenticationFromTeamsExtensionsProviderFromTeams { public static async Task<IServiceCollection> AddTeamsAuthenticationAsync(this IServiceCollection services) { var jsRuntime = services.BuildServiceProvider().GetRequiredService<IJSRuntime>(); // Is embedded in Teams App? var insideTeams = await jsRuntime.InvokeAsync<bool>("insideTeams"); // Get Teams Token if (insideTeams) { await jsRuntime.InvokeVoidAsync("microsoftTeams.app.initialize"); var token = await jsRuntime.InvokeAsync<string>("microsoftTeams.authentication.getAuthToken"); if (new JwtSecurityTokenHandler().CanReadToken(token)) { services.AddScoped<AuthenticationStateProvider>(factory => new AuthenticationStateProviderFromTeams(token)); } } return services; }using System.IdentityModel.Tokens.Jwt; using System.Security.Claims; using System.Threading.Tasks; using Microsoft.AspNetCore.Components.Authorization; public class AuthenticationStateProviderFromTeams : AuthenticationStateProvider { private string _token = string.Empty; private bool _authenticated = false; private readonly ClaimsPrincipal Unauthenticated = new(new ClaimsIdentity()); public AuthenticationStateProviderFromTeams(string token) { _token = token; } public override Task<AuthenticationState> GetAuthenticationStateAsync() { _authenticated = false; var user = Unauthenticated; var jwtToken = new JwtSecurityTokenHandler().ReadJwtToken(_token); var id = new ClaimsIdentity(jwtToken.Claims, "MyAuthentication"); user = new ClaimsPrincipal(id); _authenticated = true; return Task.FromResult(new AuthenticationState(user)); } } -
Lors de la création du manifest Teams, nous avons définit la page
wwwroot/config.htmlcomme point d’entrée lors de l’enregistrement de l’application dans Teams.<!DOCTYPE html> <html> <head> <script src="https://res.cdn.office.net/teams-js/2.0.0/js/MicrosoftTeams.min.js"></script> </head> <body style="background: white; font-family: 'Segoe UI Variable','Segoe UI','sans-serif';"> <h1>Configuration</h1> <div id="error" style="color: red;"></div> <div id="confirmation" style="display: none;"> <p>Welcome to my new application.</p> </div> <script> microsoftTeams.app.initialize().then(async () => { try { // Authenticate the user const token = await microsoftTeams.authentication.getAuthToken(); // Display the confirmation document.getElementById("confirmation").style.display = "block"; // Activate the [Save] button microsoftTeams.pages.config.setValidityState(true); // Will be made when you click on [Save]. microsoftTeams.pages.config.registerOnSaveHandler((saveEvent) => { microsoftTeams.pages.config.setConfig({ websiteUrl: window.location.origin, contentUrl: window.location.origin + "/", entityId: "MyNewApp", suggestedDisplayName: "My new app" // Tab label }); saveEvent.notifySuccess(); }); } // Error? catch (error) { document.getElementById("error").textContent = error; } }); </script> </body> </html> -
Nous pouvons maintenant sécuriser nos pages comme nous le souhaitons.
Dans
App.razornous ajoutonsCascadingAuthenticationStateetAuthorizeRouteViewcomme suit.<CascadingAuthenticationState> <Router AppAssembly="@typeof(App).Assembly"> <Found Context="routeData"> <AuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)"> <NotAuthorized> @if (context.User.Identity?.IsAuthenticated != true) { <RedirectToLogin /> } else { <p role="alert">You are not authorized to access this resource.</p> } </NotAuthorized> </AuthorizeRouteView> <FocusOnNavigate RouteData="@routeData" Selector="h1" /> </Found> <NotFound> <PageTitle>Not found</PageTitle> <LayoutView Layout="@typeof(MainLayout)"> <p role="alert">Sorry, there's nothing at this address.</p> </LayoutView> </NotFound> </Router> </CascadingAuthenticationState>Dans
Home.razor@page "/" <PageTitle>Holiday Harmony - Dashboard</PageTitle> <AuthorizeView> <Authorized> ... </Authorized> </AuthorizeView>