Nouvelles Chroniques d'Amethyste

Penser au Sens, pas au Verbe

Se connecter à une Api protégée par Azure AD avec une application native

1 commentaire

Après avoir vu comment protéger un site Web, nous allons voir comment protéger une Api Rest.

 

On pourrait penser qu’il s’agit de la même chose et de fait il y a de nombreuses similitudes.  Il existe une différence importante, c’est que le client sera assez souvent une application native: console, desktop, appli mobile, test unitaire…

Et cela change un certain nombre de choses vis à vis de OAuth et Azure AD. J’expliquais dans l’article qui précède qu’OAuth2 propose 4 schémas d’authentification. Les schémas supplémentaires sont là, entre autre, pour répondre aux besoins des clients natifs.

Pourquoi cela? On a besoin de distinguer deux types de client:

  1. les clients confidentiel (confidential client)
    Clients qui tournent sur un serveur comme une application Web. Ces clients on la capacité de conserver en toute sécurité les identifiants
    Ces clients envoient leur Id et le secret dans les requêtes pour obtenir un jeton
  2. les clients public (public client)
    Client qui tourne sur la machine ou le périphérique du client
    Il n’est évidement pas question de placer un secret dans la configuration de ce genre de client. On doit obtenir le jeton de façon indirecte, nous verrons comment

 

Voyez cet article comme la suite de celui-ci:

https://amethyste16.wordpress.com/2017/03/30/proteger-une-application-avec-azure-ad/

Je n’y reprendrai en effet pas certaines explications comme l’inscription dans Azure AD d’une application ou la récupération des configurations.

Au programme :

  • Création d’une application de démo
  • Configuration de l’authentification dans Azure
  • Démonstration des schémas de base d’OAuth2 suivants:
    Schéma autorisation via un code (Authorization Code Grant Flow)
         Schéma Autorisation serveur à serveur (Client Credential Grant Flow)
         Schéma autorisation via mot de passe (Resource owner password grant flow)
  • Consentement et permissions

 

Dans cet article je vais rester dans le cadre des applications de type line of business (LOB), c’est à dire  mono-tenantes. Attendez vous à un article plutôt dense techniquement. J’y ai passé pas mal de temps pour aller plus loin qu’un « hello world ».

 

Un dernier point, pour toute la suite, les applications seront inscrites avec un compte admin. cela a des impacts sur la gestion des consentements, mais je ne l’aborde pas dans cet article.

Création de notre Web API de démo

Notre application s’appellera DemoWebApi.

La création d’une application se fera exactement comme pour la première démo:

On n’oublie pas de choisir le modèle d’authentification Work and School Accounts, puis on laisse VS faire les déclarations nécessaires dans Azure AD.

Divers packages Nuget sont installés:

  • Microsoft.Owin.Security.Jwt
    Analyse et validation d’un jeton JWT
  • Microsoft.Owin.Security.OAuth
    Support des différents schémas OAuth2
  • Microsoft.Owin.Security.ActiveDirectory
    Middleware consacré au support des scénarios d’authentification via les technologies proposées par Microsoft.

 

Côté code:


public partial class Startup
{
   public void ConfigureAuth(IAppBuilder app)
   {
      app.UseWindowsAzureActiveDirectoryBearerAuthentication(
         new WindowsAzureActiveDirectoryBearerAuthenticationOptions
         {
            Tenant = ConfigurationManager.AppSettings["ida:Tenant"],
            TokenValidationParameters = new TokenValidationParameters {
               ValidAudience = ConfigurationManager.AppSettings["ida:Audience"]
               },
         }
      );
   }
}

On remarque des noms longs comme le bras:

  • UseWindowsAzureActiveDirectoryBearerAuthentication
  • WindowsAzureActiveDirectoryBearerAuthenticationOptions

Il doit y avoir du record là!

 

Que fait le middleware UseWindowsAzureActiveDirectoryBearerAuthentication?

Il intercepte tous les appels. S’il trouve un jeton au porteur (bearer token) dans le header d’autorisation il l’extrait, puis le valide. Il construit ensuite un ClaimsPrincipal à partir du jeton et marque l’utilisateur comme authentifié. C’est tout, rien d’autre. Et en particulier il n’y a pas de séquence d’authentification, mais nous verrons comment faire tout de même…

 

Si vous ne savez pas ce qu’est ClaimsPrincipal, vous pouvez lire ceci:

https://amethyste16.wordpress.com/2014/11/11/gerer-lauthentification-et-les-authorisations-avec-les-claims/

 

WindowsAzureActiveDirectoryBearerAuthenticationOptions est là pour passer quelques paramètres.

Les paramètre attendus dans la configuration sont:

  • ida:tenant
    Domaine associé à au tenant qui produit les jetons qui seront analysés.
    Il est de la forme <tenant>.onmicrosoft.com ou une des deux autres formes valides.
  • ida:audience
    L’audience identifie de manière unique le destinataire du jeton, il s’agira donc de la Web API dans notre cas.
    Le plus souvent il s’agira du paramètre App Id URI trouvé dans le portail. Une autre valeur possible est le ClientId.
  • ida:ClientId
    Client Id de l’application
  • ida:Password
    Le secret

 

Et pour finir le code de test:


public int Get(int id)
{
   return id + 10;
}

Récupérer les configurations

On a besoin de retrouver:

  • ClientId: 4f9ac686-b88e-4f61-b54a-…

Les points de terminaisons.

Le Guid que l’on aperçoit est le TenantId.

Déploiement de la Web Api

On publie l’Api dans un App Services et vérifie que ça fonctionne:

Configurer l’authentification

On a un besoin d’authentification. Dans l’article qui précède on avait implémenté OpenId Connect. cette fois on va faire différemment et sous-traiter le travail en question à Azure. L’appli se consacrera à la partie autorisation, pour l’essentiel, valider le jeton reçu.

Si vous souhaitez avoir plus d’infos sur ce service Azure:

https://docs.microsoft.com/fr-fr/azure/app-service/app-service-authentication-overview

 

On se rend dans le portail et le menu consacré aux App Services dans lequel on sélectionne notre déploiement.

On clique sur le menu Authentication/Authorization et on sélectionne App Service Authentication/ON.

 

Ce menu est intéressant, il permet de donner à une application Web quelconque des capacité d’authentification ou d’autorisation. Azure AD offre un support pour différents SSO comme Twitter ou Facebook. Dans le cadre de cet article on va choisir Azure Active Directory.

C’est la sélection que l’on fait dans la liste déroulante:

Lisez le petit encadré gris.

On configure ensuite le fournisseur d’authentification.

On choisit l’option Express.

  • Si l’application a déjà été inscrite dans Azure AD on sélectionne Select Existing AD App
    Pour être honnête, cette option est dure d’oreille et le portail a souvent du mal à retrouver une application, bref le plus souvent c’est uniquement cela que l’on voit

  • Si l’application n’a pas encore été déclarée dans Azure AD on choisit Create New AD App

Une fois tout cela terminé, on enregistre avec OK.

On clique sur SAVE.

Et voilà!

Il ne nous reste plus qu’à préparer une application cliente pour nous connecter à la Web Api.

 

Si vous avez besoin d’infos supplémentaires:

https://docs.microsoft.com/fr-fr/azure/app-service/app-service-authentication-overview

 

Application Console

Implémentation de l’autorisation via un code (authorization Code Grant)

Le diagramme de séquence a été présenté ici:

https://amethyste16.wordpress.com/2017/03/30/proteger-une-application-avec-azure-ad/

 

Commencez par créer une application console: DemoConsole. Pour la suite je vais m’inspirer de cet article:

https://blogs.msdn.microsoft.com/benjaminperkins/2016/10/20/how-i-connected-a-console-application-to-a-web-api-protected-by-an-azure-active-directory/

Un bon conseil: faites un premier test avec l’authentification désactivée côté Web Api (mettre en commentaire l’attribut Authorize).

 

On va donc compléter la console avec ceci:

static async Task CallWebApiUnprotectedAsync(string webApiUrl)
{
   using (HttpClient client = new HttpClient())
   {
      Uri requestURI = new Uri(webApiUrl);
      HttpResponseMessage httpResponse = await client.GetAsync(requestURI);
 
      Console.WriteLine($"HTTP Status Code: '{httpResponse.StatusCode.ToString()}'");
      Console.WriteLine($"HTTP Response: '{httpResponse.ToString()}'");
 
      string responseString = await httpResponse.Content.ReadAsStringAsync();
      var json = JsonConvert.DeserializeObject(responseString);
      Console.WriteLine($"JSON Response: {json}");
   }
}

 

Le paramètre de la fonction est l’Uri vers l’Api. Dans mon exemple:

https://localhost:44327/api/values/5

On lance et on doit voir quelque chose de similaire à:

Ca marche. Repositionnez la prise en charge des autorisations.

 

 

Notre client est une application native, on va donc plutôt faire un usage direct d’ADAL. ADAL a d’ailleurs été initialement proposé pour supporter des scénarios d’authentification d’application natives en .Net. C’est donc ce qui nous intéresse ici.

Comme tout protocole à redirection (enfin je pense), la première authentification permet d’obtenir un jeton. Le jeton sera ensuite présenté à toutes les connexions qui suivront pour ne pas avoir à ressaisir sans arrêt des credentials.

Dans le cas d’une application Web on pousserai l’utilisation d’un cookie. Ici ce n’est pas possible. Nous allons plutôt mettre un cache. Cela tombe bien, ADAL fournit la plomberie nécessaire.

 

On a juste besoin d’installer cette librairie Nuget:

Microsoft.IdentityModel.Clients.ActiveDirectory

Déclarer la console

La première étape consiste à enregistrer la console dans Azure AD comme on a appris à le faire. On enregistrera une application native.

On a pas besoin de déclarer un secret.

Récupérer les configurations

On a besoin de récupérer le ClientId de l’application console. Dans mon exemple ce sera:

Modifier CallWebApiProtectedAsync

On doit ajouter la prise en compte du workflow d’autorisation. La nouvelle version sera celle-ci:

static async Task CallWebApiUnprotectedAsync(string webApiUrl)
{
   string authority = "https://login.windows.net/7dda5ce2--...-229ad439g11e/oauth2/token"; //token endpoint
   string webApiClientId = "4f9ac686-b88e-4f61-b54a-...";
   string redirectUri = "https://localhost:1010/";
   string consoleClientId = "bd274da6-80f2-458a-b74b-...";
 
   try
   {
      PlatformParameters parameters = new PlatformParameters(PromptBehavior.Always);
 
      AuthenticationContext authContext = new AuthenticationContext(authority);
      AuthenticationResult token = await authContext.AcquireTokenAsync(webApiClientId, consoleClientId, new Uri(redirectUri), parameters);
 
      using (HttpClient client = new HttpClient())
      {
         client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(token.AccessTokenType, token.AccessToken);
         Uri requestURI = new Uri(webApiUrl);
         HttpResponseMessage httpResponse = await client.GetAsync(requestURI);
 
         Console.WriteLine($"HTTP Status Code: '{httpResponse.StatusCode.ToString()}'");
         Console.WriteLine($"HTTP Response: '{httpResponse.ToString()}'");
         string responseString = await httpResponse.Content.ReadAsStringAsync();
         var json = JsonConvert.DeserializeObject(responseString);
         Console.WriteLine($"JSON Response: {json}");
      }
   }
   catch (Exception ex)
   {
      Console.WriteLine("Exception in CallWebApiUnprotectedAsync(): " + ex.Message);
   }
}

 

On lance. Un formulaire s’ouvre:

 

Ce formulaire nous permet de saisir nos credentials. Son apparence est personnalisable avec un abonnement Premium.

Le point important à bien se souvenir est qu’il est géré ni par la Web Api, ni par l’application Console, mais par Azure AD.

 

A ce stade on devrait recevoir un message d’erreur:

 

 

AADSTS65005: Invalid resource. The client has requested access to a resource which is not listed in the requested permissions in the client’s application registration. Client app ID: bd274da6-80f2-458a-b74b-…. Resource value from request: 4f9ac686-b88e-4f61-b54a-…. Resource app ID: 4f9ac686-b88e-4f61-b54a-…. List of valid resources from app registration: 00000002-0000-0000-c000-000000000000.

 

En comparant les ClientId trouvés dans le message on conclut que l’API n’autorise pas la console à accéder à des ressources. C’est cette erreur que vous ferez assez souvent, autant la voir au moins une fois.

OAuth2 attend que la console reçoive une délégation de permission pour accéder à la Web Api.

 

Note: soyons plus précis.

Les applications natives ne sont pas authentifiées et par conséquent ne peuvent réclamer que des permissions déléguées, c’est à dire qu’il doit toujours y avoir un utilisateur réel quelque part. Une appli Web par contre est une appli confidentielle. Elle DOIT toujours s’authentifier envers Azure AD avec un jeton d’accès (access token). Cela signifie qu’elles ont également la possibilité de réclamer des permissions spécifiques à l’application. Comparez par exemple la situation entre l’application console et le site web construit dans l’article précédent:

 

 

Je reviendrai la dessus sur la section consacrée au consentement.

 

Revenons au portail.

  • on se rend dans les paramétrages Azure AD de la console
  • On clique sur Required Permission

  • On clique sur ADD puis Select an Api

  • On entre le nom de notre Api: DemoWebApi dans mon cas et on la sélectionne dans la liste puis SELECT

  • On la sélectionne ensuite dans le menu Select permission.
    Cela a pour effet de placer la web api parmi les applications pour lesquelles le site demande l’autorisation d’accéder. Une ligne supplémentaire sera apparente dans le formulaire de consentement.

  • On fait DONE pour enregistrer

 

La situation finale est la suivante:

Dans les coulisses on fait une déclaration dans un objet .

Si on ouvre le manifeste de l’application DemoConsole:

On trouve ceci:

db023…. est le client Id de l’Api Web.

 

On relance la console et cette fois:

On accepte.

A la fin ceci doit s’afficher:

 

Analyse du code

Revenons un moment sur le code.

Premier point important: webApiUrl DOIT être une uri en HTTPS et pas HTTP.

on utilise 4 paramètres:

  1. authority
    C’est le point de terminaison pour obtenir un jeton
  2. webApiClientId
    Client Id de l’Api Rest
    Mais le code fonctionne de la même manière si à la place on met le App Id Uri qui identifie lui aussi l’appli de façon unique.
  3. redirectUri
    Reply Url choisit lors de la déclaration de la console. Il n’y a pas besoin que ce soit une adresse réelle, il faut juste que ce soit un format d’url
  4. consoleClientId
    Client Id de la console

J’ai détaillé dans l’article précédent comment obtenir ces informations.

PlatformParameters détermine la façon dont s’affichera le formulaire de saisie des credentials.

  • Always
    S’affiche toujours
  • Auto
    Ne s’affiche que si le jeton a expiré

Par défaut, le SDK Adal gère automatiquement la mise en cache des jetons, mais on peut être amené à le vider ainsi:


authenticationContext.TokenCache.Clear();

 

Pour le reste, tout ce que l’on a à faire est d’appeler les méthodes en AcquireToken qui gèrent pour nous le cache et le jeton de renouvellement.

Je reparlerai du cache plus loin, dans la section consacrée au consentement.

 

Les lignes 12 et 13 obtiennent un jeton d’accès.

On pourrait d’ailleurs le récupérer dans un format prêt à l’emploi dans une requête HTTP en appelant:


string authHeader = token.CreateAuthorizationHeader();

On obtient une chaîne de la forme:

Bearer eyJ0eXAiOiJK…

 

Dans la démo on utilise une autre méthode, mais cela revient au même.

 

Le reste du code est simplement une façon de vérifier que le jeton reçu est bien valide en appelant une des Api du site.

Implémentation de l’autorisation serveur à serveur (Client Credentials Grant)

On va repartir de ce qui a été installé dans le chapitre précédent, je vais donc moins détailler.

 

Le problème que pose la méthode démontrée précédemment est que l’application cliente doit répondre à un chalenge.

C’est potentiellement par exemple pour un script, des tests unitaires ou autres situations où l’on souhaiterai retrouver le confort de la saisie directe d’un credentials plutôt que passer par un intermédiaire.

Dans ce schéma le propriétaire de la ressource va donc saisir les credentials dans une interface gérée par le client. cela suppose donc que l’on fasse parfaitement confiance au client et en particulier que l’on soit certain qu’il n’enregistre pas le mot de passe quelque part.

Voici comment il fonctionne:

C’est un flux extrêmement simple comme on le voit.

Note: Il n’y a pas de jeton de renouvellement. En fait il n’y en a guère besoin puisque l’on dispose des credentials applicatifs qui permettent de réclamer un jeton d’accès quand on le souhaite.

 

Voici un exemple de code:

static async Task CallWebApiUnprotectedAsync(string webApiUrl)
{
   string authority = "https://login.microsoftonline.com/7dda5ce2-2fb6-4f82-bc27-...";
   string webApiClientId = "db023429-be6f-413b-b1ee-...";
   string secret = "Eyc10keEhm4/Wtj...ix+w3Pg=";
 
   // obtention du token
   AuthenticationContext authenticationContext = new AuthenticationContext(authority, false);
   ClientCredential credentials = new ClientCredential(webApiClientId, secret);
   AuthenticationResult res = await authenticationContext.AcquireTokenAsync(webApiClientId, credentials);
 
   // et pour tester on appelle l'Api
   try
   {
      using (HttpClient client = new HttpClient())
      {
         client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(res.AccessTokenType, res.AccessToken);
         Uri requestURI = new Uri(webApiUrl);
         HttpResponseMessage httpResponse = await client.GetAsync(requestURI);
 
         Console.WriteLine($"HTTP Status Code: '{httpResponse.StatusCode.ToString()}'");
         Console.WriteLine($"HTTP Response: '{httpResponse.ToString()}'");
         string responseString = await httpResponse.Content.ReadAsStringAsync();
         var json = JsonConvert.DeserializeObject(responseString);
         Console.WriteLine($"JSON Response: {json}");
      }
   }
   catch (Exception ex)
   {
      Console.WriteLine("Exception in CallWebApiUnprotectedAsync(): " + ex.Message);
   }
}

 

Le secret est un secret enregistré avec l’Api Web. Il tient lieu de mot de passe.

On voit immédiatement la faiblesse de cette méthode: on est obligé de partager un credentials avec la Web Api. Cette méthode doit être évitée si possible. Elle n’est envisageable que pour des applications confidentielles (un site Web) ou des applications natives développées par votre propre organisation. Il est important de garder le contrôle de ce qui est fait des credentials.

 

Un petit coup d’œil avec Fiddler nous donne quoi?

La demande de jeton d’accès:

POST https://login.microsoftonline.com/7dda5ce2-2fb6-4f82-bc27-…/oauth2/token HTTP/1.1
Accept: application/json
x-client-SKU: PCL.Desktop
x-client-Ver: 3.13.9.1126
x-client-CPU: x64
x-client-OS: Microsoft Windows NT 6.2.9200.0
x-ms-PKeyAuth: 1.0
client-request-id: 4889a3b9-8193-4bc9-8fbf-…
return-client-request-id: true
Content-Type: application/x-www-form-urlencoded
Host: login.microsoftonline.com
Content-Length: 187
Expect: 100-continue
Connection: Keep-Alive
resource=db023429-be6f-413b-b1ee-…&client_id=db023429-be6f-413b-b1ee-…&client_secret=Eyc10keEhm4%2FWtjOrvRTM…uix%2Bw3Pg%3D&grant_type=client_credentials
J’ai surligné les 4 paramètres que l’on doit s’attendre à retrouver:
  1. resource
    Identifie la ressource cible, la web api en fait
  2. client_id
    L’identifiant de la console dans Azure AD
  3. client_secret
    Le secret
  4. grant_type
    client_credentials dans ce schéma

 

Nous savons qu’il s’agit d’une demande de jeton d’accès car la requête est postée sur le point de terminaison:

https://login.microsoftonline.com/7dda5ce2-2fb6-4f82-bc27-…/oauth2/token

 

La réponse reçue est la suivante:

HTTP/1.1 200 OK
Cache-Control: no-cache, no-store
Pragma: no-cache
Content-Type: application/json; charset=utf-8
Expires: -1
Server: Microsoft-IIS/8.5
Strict-Transport-Security: max-age=31536000; includeSubDomains
X-Content-Type-Options: nosniff
client-request-id: 851f3975-d04a-4d0b-9433-346b0e9dbc87
x-ms-request-id: 9913ef49-e1d5-4ba7-848e-e5e8644e2f00
P3P: CP= »DSP CUR OTPi IND OTRi ONL FIN »
Set-Cookie: esctx=AQABAAAAAABnfiG-mA6NTae7CdWW7QmsAsPAv…O3tlPC_fgxMnXEP0I6g_MhywuZRwgwgAA; domain=.login.microsoftonline.com; path=/; secure; HttpOnly
Set-Cookie: x-ms-gateway-slice=008; path=/; secure; HttpOnly
Set-Cookie: stsservicecookie=ests; path=/; secure; HttpOnly
X-Powered-By: ASP.NET
Date: Mon, 03 Apr 2017 20:25:33 GMT
Content-Length: 1450
{« token_type »: »Bearer », »expires_in »: »3599″, »ext_expires_in »: »10800″, »expires_on »: »1491254734″, »not_before »: »1491250834″, »resource »: »db023429-be6f-413b-b1ee-… », »access_token »: »eyJ0eXAiOiJKV1QiLCJhbG… »}

L’élément important est le jeton au format JWT. On peut le décoder sur le site jwt.io par exemple. la partie intéressante est ici:

  • aud: l’audience, son id client
  • iss: le STS
  • tid: l’id du tenant

 

Puis la requête vers l’Api qui ne dépend en rien du modèle d’authentification:

GET https://demowebapi12.azurewebsites.net/api/values/5 HTTP/1.1
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6ImEzUU4wQlpTN3M0bk4tQmRyamJGM…
Host: demowebapi12.azurewebsites.net

 

Il est important de remarque que les ressources sont réclamées dans le contexte de l’application, pas de l’utilisateur. Cela signifie que la ressource ne verra arriver qu’une seule identité: celle de l’appli et n’a aucun moyen de savoir qui se connecte réellement.

Parfois cela peut être un problème pour l’application comme expliqué dans cet article:

https://dzimchuk.net/protecting-your-apis-with-azure-active-directory/

 

Implémentation de l’autorisation via mot de passe (Resource Owner Password Credential Grant)

Introduction

L’échange du secret du site est acceptable si le client est une application confidentielle ou une application serveur, mais tout de même un peu violent pour un client public comme une appli console.

Ne serait t’il pas plus judicieux d’utiliser le login/mot de passe du propriétaire de la ressource. C’est même plutôt logique si celui-ci souhaite accéder, via l’application cliente, à des ressources dont il est le propriétaire. Il ne s’étonnera pas de saisir ses credentials.

Le source est très similaire à celui vu précédemment. Je ne vais d’ailleurs reproduire que la partie obtention du jeton. Le reste (l’appel à l’Api) est en rien différente de ce qui a été rencontré dans les deux exemples déjà donnés.

 

Ce scénario a diverses limitations et dangers dont on trouvera un panorama ici:

http://www.cloudidentity.com/blog/2014/07/08/using-adal-net-to-authenticate-users-via-usernamepassword/

Lisez en particulier le chapitre sur le MSA.

 

Le nouveau code est le suivant:

string login = "fmirouze@....";
string password = "....";
 
AuthenticationContext authenticationContext = new AuthenticationContext(authority, false);
UserPasswordCredential credentials = new UserPasswordCredential(login, password);
AuthenticationResult res = await authenticationContext.AcquireTokenAsync(webApiClientId, consoleClientId, credentials);

 

Le login doit être un nom UPN.

Le lien précédent indique ceci:

Microsoft accounts that are used in the context of an AAD tenant (classic example: Azure admins) cannot authenticate to AAD via raw credentials – they MUST use the interactive flow (though the PromptBehavior.Never flag remains an option).

 

Comme dans ce scénario je ne souhaite pas utiliser le flux interactif, on va créer un nouvel utilisateur dans le tenant plutôt que de tester avec le compte admin du tenant.

Pour le cas où vous vous posez la question, l’emploi d’un compte comme celui de l’administrateur du tenant lève une erreur HTTP pas très fréquente:

HttpRequestException:  Response status code does not indicate success: 406 (NotAcceptable).

Je ne sais pas vraiment pourquoi cette erreur précise est levée.

 

Consentement et permissions

Si vous lancez le code en l’état, vous obtenez un message d’erreur:

AdalServiceException: AADSTS65001: The user or administrator has not consented to use the application with ID ‘bd274da6-80f2-458a-b74b-…. Send an interactive authorization request for this user and resource.

 

Voyons un peu. Il existe deux types de schéma OAuth2:

  1. les schémas interactifs
  2. les schémas non interactifs

 

Dans les schémas interactif, le propriétaire de la ressource doit donner son consentement ce qui se traduit par l’ouverture de ce genre de formulaire:

On a également deux types de consentement

  1. user consent (consentement utilisateur)
    C’est l’utilisateur lui-même qui accorde la permission. La permission est accordée dans le contexte de l’utilisateur
  2. admin consent (consentement par l’admin)
    C’est l’admin qui accorde la permission. La permission est accordée à tous les utilisateurs

 

Le fait qu’une permission puisse ou pas être accordée à un utilisateur ou par l’admin est un choix du développeur de l’application. On retrouve ces infos dans le portail. Par exemple la Web Api accorde ceci à l’application intitulée: Windows Azure Active Directory (Microsoft.Azure.ActiveDirectory). En gros c’est l’accès à Graph.

 

 

Regardez la dernière colonne « Requires admin« , certaines sont marquées YES en vert et d’autres NO en rouge. Cela veut dire quoi?

Il existe en fait deux types de permissions:

  1. Autorisations de l’application
    L’appli cliente peut accéder à l’appli web en tant que telle, c’est à dire hors contexte utilisateur.
    Ce type d’autorisation nécessite le consentement d’un administrateur et n’est pas disponible pour les applications clientes natives comme notre console
    Ce sont les permissions YES en vert
  2. Autorisations déléguées
    L’application cliente doit accéder à l’Api en tant qu’utilisateur connecté, mais limité par l’autorisation sélectionnée. Il faut en plus que l’utilisateur dispose lui même de cette permission.
    Ce type d’autorisation est accordé dans le contexte d’un utilisateur particulier et est accessible aux applications natives.
    Ce sont les permissions marquées NO en rouge.

Jetez un œil sur les libellés. Par exemple, la permission « Sign in and read user profile » est activée par défaut pour toutes les applications.

 

Note: ces libellés changent de temps à autre au grès des mises à jour d’Azure AD

 

Le consentement utilisateur par défaut est user content. Ce que l’on constate dans le portail Azure AD:

 

Dans le menu User Settings on regarde ce qui est paramétré dans « User can allow apps to access their data« . Concrètement n’importe quel utilisateur peut installer une appli trouvée on ne sait où, l’installer dans son Access panel et la laisser accéder à ses infos. Si vous êtes admin je pense que vous êtes en réa en ce moment!!!!

Tant que l’on y est, notez aussi le « user can register applications » un peu plus bas. Par défaut, un utilisateur non admin peut enregistrer une application dans Azure AD. C’est ce que l’on fait typiquement avec VS.

 

Alors que faire? Il y a 2 options:

  1. l’admin se connecte une fois en mode interactif et fait le consentement pour tous les utilisateurs
  2. un utilisateur fait ce consentement pour son propre compte
    On ne peut pas faire de consentement utilisateur dans le cas où l’on passe directement, cad sans passer par un serveur d’autorisation, un login/mot de passe
    On est dans ce cas ici

 

La première option peut se lancer depuis le portail:

 

Dans le panneau SETTINGS de l’application native. On sélectionne Required permissions, puis on clique sur Grant Permissions. Il s’ouvre:

 

On clique sur YES.

Si maintenant on relance l’appli une bonne surprise nous attend:

Ca fonctionne!

 

Une autre possibilité est de lancer cette url depuis un navigateur par exemple:

https://login.microsoftonline.com/<tenantId>/oauth2/authorize?<client_id=client_id>&response_type=code&redirect_uri=sample://callback&nonce=1234&resource=https://graph.windows.net&prompt=admin_consent

 

On sait ce qu’est le tenantId, client_id est l’id client de la console dans notre exemple. L’url est simplement l’url d’autorisation, mais avec en plus le paramètre prompt=admin.

On arrive sur un formulaire similaire à celui-ci:

On sélectionne un compte administrateur dans le tenant cible:

On arrive à un formulaire de consentement qui ressemble beaucoup à ceux déjà rencontré. Il est toutefois différent.

Observez le texte encadré. Ce texte apparaît lorsque l’on fait un consentement administrateur.

 

Il est également intéressant de savoir où est stockée le consentement.

Pour une application native, il est stocké dans le jeton de renouvellement. Le jeton de renouvellement est par défaut stocké dans un cache par le SDK ADAL. C’est pour cela que l’on a besoin de faire une seule fois le consentement.

Par défaut ce cache est un cache en mémoire. Cela signifie que tant que le jeton de renouvellement n’a pas expiré et/ou le cache n’a pas été vidé ou le processus relancé, tout va bien, autrement il faut refaire le consentement. Il est tout à fait possible d’injecter un cache personnalisé qui offre un stockage plus persistant que la mémoire.

Pour les applications Web, le cache est automatiquement géré dans un stockage persistent.

Bibliographie

 

 

 

 

 

Une application Web

https://yossidahan.wordpress.com/2014/07/31/using-ad-for-authentication-between-web-app-and-web-api/

 

https://aadguide.azurewebsites.net/integration/webapisingletenant/

 

 

Publicités

Une réflexion sur “Se connecter à une Api protégée par Azure AD avec une application native

  1. This is by far the most descriptive example I could see please do translate the images/blog in english to make it more clear.

Laisser un commentaire

Entrez vos coordonnées ci-dessous ou cliquez sur une icône pour vous connecter:

Logo WordPress.com

Vous commentez à l'aide de votre compte WordPress.com. Déconnexion / Changer )

Image Twitter

Vous commentez à l'aide de votre compte Twitter. Déconnexion / Changer )

Photo Facebook

Vous commentez à l'aide de votre compte Facebook. Déconnexion / Changer )

Photo Google+

Vous commentez à l'aide de votre compte Google+. Déconnexion / Changer )

Connexion à %s