Nous allons aborder ici le flow principal, qui est le plus utilisé, le plus sécurisé mais aussi sans doute le plus complexe à comprendre. Il s’applique principalement aux applications orientées serveur, c’est-à-dire où le code source n’est pas public et peut donc utiliser des secrets pour s’authentifier auprès du service de token.

Notons que ce flux a légèrement « évolué » : on le retrouve souvent implémenté avec la partie PKCE qui permet de remplacer le secret par un système d’échange de clés. En effet, comme expliqué dans un précédent article, ce flux est aussi dorénavant utilisé dans les applications mobiles et les application monopages (SPA) qui ne peuvent – et ne doivent – pas stocker de secrets (applications publiques). Nous obtenons donc les flux suivants :

Schéma de fow principal

 

Prenons les requêtes en détails. Il s’agit d’abord de rediriger l’utilisateur sur la page de login du provider d’identité (après avoir créé une application Azure AD bien sûr) :

https://login.microsoftonline.com/REPLACE_WITH_TENANT_ID/oauth2/v2.0/authorize?client_id=REPLACE_WITH_CLIENT_ID&response_type=code&redirect_uri=https://localhost&response_mode=query&scope=openid&state=12345

 

Après s’être authentifié, l’utilisateur est redirigé vers le redirect URL renseigné (soit « localhost » pour notre test). On y retrouve un code, le fameux « authorization code » :

https://localhost/?code=AUTHORIZATION_CODE&state=12345&session_state=67ae283c-884a-46b2-ba3b-300c11133631

 

Petite astuce : vous pouvez copier cette url dans Postman pour en extraire les données :

 

extraction de données url postman

 

Ce qui nous intéresse donc, c’est le code : il va nous servir pour la seconde étape, c’est-à-dire récupérer un ou plutôt des tokens auprès du fournisseur d’identité. En effet, dans ce flow, nous pouvons récupérer jusqu’à 3 tokens :

  • l’access token;
  • le refresh token (en passant le scope « offline_access ») ;
  • mais aussi l’ID token dans le cadre d’un flow hybride open ID (voir l’article consacré à ces notions). Pour cela, il faut interroger le endpoint token du fournisseur d’identité à l’aide du requête POST (à l’aide d’un requêteur type Postman) :
https://login.microsoftonline.com/REPLACE_WITH_TENANT_ID/oauth2/v2.0/token

Avec les paramètres suivants :

récupération de token

Nous obtenons donc une réponse au format JSON contenant les 1,2 ou 3 token(s) demandé(s) !

 

Access refresh ID token authorization

Coté code, de nombreuses bibliothèques nous offrent heureusement le niveau d’abstraction nécessaire pour ne pas avoir à gérer ces échanges mais il reste important de savoir « comment ça marche sous le capot ».

Prenons l’exemple d’une API sur laquelle nous allons poser les briques nécessaires pour l’authentification. Comme le code est simple, nous irons un peu plus loin en mettant en place un exemple du flux On behalf of.

Dans un premier temps, nous allons créer une application .net Core (web ou API) :

 

API net core

Puis ajouter le package nuget :

Microsoft Identity web

Ensuite, ajoutons les couches de service correspondant à notre type d’application :

  • Pour une web app :
1. services.AddMicrosoftIdentityWebAppAuthentication(Configuration);
  • Pour une API :
1. services.AddMicrosoftIdentityWebApiAuthentication(Configuration);

 

Il faut ensuite ajouter à la configuration (AppSettings.json) les information nécessaires de notre application Azure AD (la même que précédemment créée) :

A noter : il faut bien sûr ajouter l’URL de développement local à votre application 😉

1.  "AzureAd": {      
2.    "Instance": "https://login.microsoftonline.com",
3.    "ClientId": "REPLACE_WITH_ CLIENT_ID",
4.    "TenantId": "REPLACE_WITH_ TENANT_ID",
5.    "ClientSecret": "REPLACE_WITH_CLIENT_SECRET"
6.  },

 

Puis on l’applique sur nos requêtes HTTP en ajoutant :

1. app.UseAuthentication();

On protège ensuite notre application en ajoutant des autorisations sur les pages ou méthodes d’API souhaitées (attribut [Authorize]) ou sur toute l’application avec un filtre sur les controller par exemple.

Dans le cas de l’application web, celle-ci nous redirige vers la page de login (/authorize) du tenant Azure AD configuré, nous permettant de nous connecter puis d’accéder à notre page. En ajoutant un « @User.Identity.Name » à notre vue, nous pouvons donc afficher le nom de l’utilisateur connecté :

L’API nous répond alors un joli « 401 ». En effet, dans le cas des APIs, on n’authentifie pas l’utilisateur connecté mais on vérifie qu’il a bien accès à nos ressources protégées. Pour cela, il suffit de générer un token à partir d’une application Azure AD ayant accès à ces ressources.

Il suffit d’utiliser un des flux Oauth vus précédemment (implicit ou auth code) et de préciser le scope avec l’ID de l’application API (qui doit bien sûr être consentie sur votre application cliente) :

ID appli API Microsoft Azure AD

Nous récupèrons ensuite le token à la fin du flux choisi, puis on le présente à notre API :

présentation token API

L’API nous répond bien en nous donnant accès à ses données protégées.

Imaginons maintenant que notre API doive accéder à une autre API, elle-même protégée par le même mécanisme :

  • Elle ne peut pas connecter l’utilisateur puisque celui-ci n’a aucune interaction avec cette API ; il passe par son application cliente.
  • Elle ne peut pas utiliser le token fourni par l’application cliente : celui-ci sera rejeté car il n’a pas été généré pour elle mais pour l’application cliente (les clientID seront différents et donc le token sera rejeté).

Pour répondre à ce besoin, il existe une extension Oauth2 peu connue (et par conséquent rarement utilisée) appelée Token Exchange. Elle permet de régénérer un token pour notre application à partir d’un token d’une autre application. Il est évidemment nécessaire qu’une relation de confiance entre les deux applications ait été mise en place. L’implémentation sur la plateforme d’identité Microsoft se nomme le flux on behalf of. Il permet, à partir d’applications Azure AD, d’établir cette relation de confiance. Pour cela, il suffit d’ajouter l’ID de notre application client dans le manifest de l’application de l’API (pas encore d’interface dans le portail) en tant qu’application cliente connue :

ID client application API

Cela permettra lors du consent à Azure AD de présenter à l’utilisateur l’ensemble des permissions et scopes demandés.

Consent Azure AD permission

Lors de l’authentification (comme précédemment), nous retrouvons donc le consent « combiné » avec les deux Applications Azure AD (« MyClient » et « MyGenAPI ») :

 

mygenAPI MyGenClient Microsoft

Pour tester cette configuration, il nous faut une API appelant une autre API : nous allons donc ajouter un peu de code pour appeler le Graph Microsoft :

On installe le package Nuget :

package nugets microsoft

On ajoute ensuite le service permettant l’échange de token à notre service authentification Microsoft :

1. services.AddMicrosoftIdentityWebApiAuthentication(Configuration).EnableTokenAcquisitionToCallDownstreamApi(options => {
2.               Configuration.Bind("AzureAd", options);
3.           }).AddInMemoryTokenCaches(); 


L’appel au Graph est ajouté directement dans le code du contrôleur (par souci de simplicité), cette implémentation n’est pas à reprendre…

D’abord les références :

1. using System;
2. using System.Collections.Generic;
3. using System.Linq;
4. using System.Net.Http.Headers;//pour ajouter le bearer dans la requête
5. using System.Threading.Tasks;
6. using Microsoft.AspNetCore.Authorization;
7. using Microsoft.AspNetCore.Mvc;
8. using Microsoft.Extensions.DependencyInjection;//pour récupérer le service d’aquisition de token
9. using Microsoft.Graph; // pour le graph client 😊
10. using Microsoft.Identity.Web;// La definition du service d’aquisition de token

 

Ensuite, la méthode – ici le service ITokenAcquisition – va nous permettre, en appelant la méthode GetAccessTokenForUserAsync, de récupérer un token pour notre application au nom de l’utilisateur connecté sur l’application cliente :

1. [HttpGet]
2. [Authorize]
3. public async Task<IEnumerable<WeatherForecast>> Get()
4. {
5.            var tokenAcquisition = 
HttpContext.RequestServices.GetRequiredService<ITokenAcquisition>();
6.            GraphServiceClient graphClient = new GraphServiceClient(
7.            new DelegateAuthenticationProvider(async (request) => {
8.                var token = await tokenAcquisition
9.                    .GetAccessTokenForUserAsync(new string[]{"User.Read"}, user: HttpContext.User);
10.                request.Headers.Authorization =
11.                    new AuthenticationHeaderValue("Bearer", token);
12.            })
13.        );
14.            User lg = await graphClient.Me.Request().Select(u => new
15.            {
16.                u.DisplayName,
17.                u.Mail,
18.                u.UserPrincipalName,
19.                u.Country
20.            }).GetAsync();
21.            var rng = new Random();
22.            return Enumerable.Range(1, 5).Select(index => new WeatherForecast
23.            {
24.                Date = DateTime.Now.AddDays(index),
25.                TemperatureC = rng.Next(-20, 55),
26.                Summary = Summaries[rng.Next(Summaries.Length)]+" In "+ lg.Country
27.            })
28.            .ToArray();
29. }
30.

 

En appelant cette méthode dans Postman avec notre token client, nous avons accès à information de l’API complétée avec celle du Graph (le pays) :

API postman microsoft Graph

On peut vérifier le nouveau token sur jwt.io en le récupérant dans le debugger :

récupération token debugger microsoft AD

Je retrouve donc bien un token généré pour mon API au nom de l’utilisateur connecté sur mon application cliente :

 

 

Une démo en vidéo pour aller plus loin !

Avec le contenu de ce billet, nous avons vu l’ensemble des possibilités pour sécuriser vos applications (Web et API) à l’aide des flux Oauth et de la plateforme d’identité Microsoft. Si vous souhaitez mettre en pratique les méthodes présentées tout au long de cette série dédiée à l’Identité, n’hésitez pas à regarder le replay de notre webinaire en cliquant sur le bandeau ci-dessous !

Azure AD par la pratique