Loupe

Azure Functions Http et sécurité : utiliser un Azure AD

Dans cet article, nous allons voir comment sécuriser l'accès à une Azure Function Http en utilisant la fonctionnalité "Authentication / Authorization" présente sur chaque AppService. 

TL; DR;

On peut en quelques clics et gratuitement mettre en place une authentification sur n'importe quel AppService. Avec un peu de configuration, on peut aussi s'y connecter depuis du javascript (ou n'importe quelle application distante).

Sur Azure...

Partons du principe que nous avons créé une Azure Function Http : on peut donc la déclencher via un appel Http classique et elle va nous retourner un résultat de son choix. Dans mon cas, je créé une fonction HttpTrigger1 sur un AppService CallMeMaybe-authentication depuis le portail et elle va utiliser du code très simple mais que je peux déclencher depuis le portail Azure... pratique pour les tests!

Capture d’écran 2020-06-12 à 20.17.52.png

Ce que nous allons voir est valable sur un AppService, vous pourriez donc en faire de même pour une Api classique !

Configuration de l'App Service

Nous allons configurer notre AppService pour permettre une authentification de l'utilisateur via Azure Active Directory(AAD). Pour cela, on se rend sur la section "Authentication / Authorization" de l'app service et on active l'App Service Authentication.

Capture d’écran 2020-06-12 à 20.00.42.png

Dans la liste de choix qui apparaît, on choisit alors de demander la connexion de l'utilisateur avec AAD si ce n'est pas déjà le cas. Maintenant, on clique sur Azure Active Directory et on choisit le mode express qui va créer automatiquement pour nous une application dans l'AD de notre tenant Azure. Une fois ceci effectué, on n'oublie pas de sauvegarder :)

Capture d’écran 2020-06-12 à 20.01.14.png

Une fois ceci fait, tout appel à votre Azure Function HttpTrigger devra être authentifié. Ainsi, si j'ouvre l'URL de mon AF Http dans un navigateur, j'ai une mire de connexion qui s'affiche et on me demande si j'autorise ou non l'application à accéder à mon profil. 

Capture d’écran 2020-06-12 à 20.10.33.png

Une fois que j'ai donné mon accord, le résultat de mon AF s'affiche. On vient donc en quelques secondes de mettre en place une sécurisation de notre Azure Function http !

Bon, c'est parfait tout cela, mais dans les faits, il est rare que l'on demande à l'utilisateur d'appeler directement les endpoints de nos Apis dans son navigateur. Il va donc falloir faire un peu de configuration de notre application AAD.

Configuration de l'Azure Function et de l'application AAD

La première erreur que l'on risque d'avoir en appelant l'Api est celle-ci : 

Access to XMLHttpRequest 
at 'https://ddd-authentication.azurewebsites.net/api/HttpTrigger1' 
from origin 'http://localhost:8080' has been blocked 
by CORS policy: No 'Access-Control-Allow-Origin' 
header is present on the requested resource.

Capture d’écran 2020-06-12 à 20.27.29.png

Pour corriger ce problème, on va dans la section "CORS" et on rajoute l'url de notre front javascript : 

Capture d’écran 2020-06-12 à 20.29.30.png

Ensuite, il va falloir aller configurer l'enregistrement de l'application correspondant à votre AppService dans l'AAD pour mettre l'url de votre front comme Url de redirection post authentification : 

Capture d’écran 2020-06-12 à 21.28.47.png

Un peu plus bas, il faudra cocher les cases Access Token et Id Token qui nous serons utiles plus tard.

Capture d’écran 2020-06-12 à 22.26.33.png

Si vous souhaitez que des identités n'appartenant pas à votre tenant puissent se connecter, il faut activer l'option multi-tenant un peu plus bas sur ce même écran. Cela peut être notamment le cas si vous souhaitez que n'importe quelle personne avec un compte Microsoft puisse se connecter. (Si vous rencontrez des erreurs à la sauvegarde, cette réponse StackOverflow pourrait vous être utile).

Capture d’écran 2020-06-12 à 22.27.43.png

Une fois que cela est fait, il vous faut aller noter et mettre de côté deux informations présentes sur la section "Expose an Api" :

Capture d’écran 2020-06-12 à 22.30.52.png

Cette "Application Id Uri" sera utilisée comme audience du jeton d'accès à l'Api. Il faut donc aller dire à votre AppService de reconnaître cette audience : dans la section "Authentication / Authorization", on reclique sur Azure Active Directory, on ajoute l'audience dans cette liste puis on sauvegarde.

Capture d’écran 2020-06-12 à 22.34.24.png

On en a maintenant terminé avec toutes ces étapes de configuration ultra intuitives (sic...) et on va pouvoir faire un peu de code !

Faire un appel en javascript

Pour cela, j'utilise la librairie Msal de Microsoft. Prenez bien la dernière version car Microsoft apporte régulièrement des patchs de sécurité (comme l'utilisation du code-flow + PKCE dont on a déjà parlé avant dans un article). La liste complète des versions est présente sur le Github du projet. Il suffit d'ajouter cette balise dans votre html :

<script 
  type="text/javascript"
  src="https://alcdn.msftauth.net/lib/1.3.0/js/msal.js"
  crossorigin="anonymous">
</script>

Le code pour se connecter suivra alors cette logique :

  1. Je vérifie si je suis connecté en récupérant l'utilisateur courant.
  2. Si je ne suis pas connecté, je demande une connexion de l'utilisateur dans la même page.
  3. Une fois connecté, je demande un jeton d'accès afin de pouvoir accéder aux AFs.
  4. Je peut maintenant faire mes appels avec le jeton.
// Laissons MSAL interprété les redirection vers notre page
myMSALObj.handleRedirectCallback((error, response) => {});

// récupération de l'utilisateu courant
const user = myMSALObj.getAccount();

// Pas connecté, je demande une connexion
if (!user) {
  const loginRequest = { scopes: ['openid'] };
  myMSALObj.loginRedirect(loginRequest);
  return;
}


// si je suis ici, je suis connecté == je demande un jeton d'accès
const tokenRequest = {
  scopes: ['https://dd.onmicrosoft.com/callmemaybe/user_impersonation']
};
myMSALObj.acquireTokenSilent(tokenRequest)
  .then((value: Msal.AuthResponse) => {

  // Appel à l'Azure function en utilisant le token
  $.ajax(
    {
      headers: {'Authorization': `Bearer ${value.accessToken}`},
      type: 'GET',
      url: urlVersLAF,
      success: (data) => {
        console.log('success ' + data);
      },

      error: (jqXHR1, textStatus, errorThrown) => {

        console.log('error ' + textStatus + ' - ' + errorThrown);
      }
    }
  )
}).catch(error => {
  // on a pas réussi à obtenir le token de manière silencieuse :
  // réessai en mode "redirection"
  return myMSALObj.acquireTokenRedirect(tokenRequest);
});

Accéder aux informations sur l'utilisateur connecté

Ici aussi c'est finalement simple : il suffit d'ajouter un paramètre de type ClaimsPrincipal à notre AzureFunction. Ce dernier sera rempli automatiquement par le runtime. Voici un code tout bête permettant d'inspecter ce qu'on a dedans :

public static async Task<IActionResult> Run (
    HttpRequest req, ILogger log, ClaimsPrincipal principal) {

    string responseMessage = string.Empty;
    foreach (Claim claim in principal.Claims) {
        responseMessage 
        += claim.Type + " - " 
        + claim.Value + System.Environment.NewLine;
    }

    return new OkObjectResult (responseMessage);
}

Nous aurons alors ce type d'informations retournées par notre fonction :

aud -  https://monTenant.onmicrosoft.com/callmemaybe
iss -  https://sts.windows.net/9c9fb00d-5eee7efb6bf5/
http://schemas.microsoft.com/claims/authnclassreference -  1
aio -  AUQAu/8PAAAAUzxKkKcsOtp8nPwr+enDDchcsA3FmqC585S0W8yk5mDIsstF2NV4Zuu5HxtwqDHBr7oAYY69G4fT7bIaXaYmxQ==
http://schemas.microsoft.com/claims/authnmethodsreferences -  pwd
appid -  9bd6a8e1-62ca-4f00-b223-0fcea60e2d07
appidacr -  0
http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress -  jantoine@infinitesquare.com
http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname -  ANTOINE
http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname -  Jonathan PRO
http://schemas.microsoft.com/identity/claims/identityprovider -  https://sts.windows.net/7c7020c6-d3ac-ddd-b644-32bb956b9753/
name -  Jonathan ANTOINE
http://schemas.microsoft.com/identity/claims/objectidentifier -  c5896f20-8e42-ddd-9f0f-9495326f8f9d
rh -  0.AQwADbCfnM5TK0dddpvKYgBPsiMPzqYOLQcMAAc.
http://schemas.microsoft.com/identity/claims/scope -  user_impersonation
http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier -  78VdmMtFq9zF4sUAddNMuXwvdL_OL8ekVfNew08
http://schemas.microsoft.com/identity/claims/tenantid -  dddd-53ce-dddd-ddd-ddddd
http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name -  jantoine@infinitesquare.com
uti -  4mN4ybdddddN-grR7TlEAA
ver -  1.0

On peut y retrouver facilement les nom, prénom et email de l'utilisateur : de quoi se débrouiller ;)

Limiter l'accès à certains utilisateurs

Avec ce que l'on vient de faire, toutes les personnes de votre AD peuvent se connecter à votre Azure Function. Il existe un moyen simple d'en limiter l'accès qu'à certaines personnes via le portail Azure en activant ce switch dans l'AppRegistration de votre application AAD :

Capture d’écran 2020-06-15 à 13.08.54.png

Si l'utilisateur n'a pas accès, vous obtiendrez ce message d'erreur :

The signed in user '{EmailHidden}' is not assigned to a role
for the application '9bddd3-0fcea60e2d07'(CallMeMaybe-authentication).

Dans votre javascript, vous pouvez modifier handleRedirectCallback pour gérer ce cas. Voici un exemple efficace : 

let errorInSignin = false;
myMSALObj.handleRedirectCallback((error, response) => {
    if (error) {
        errorInSignin = true;
        window.alert(error);
    }
});
if (errorInSignin) {
    return;
}

Série

Cet article fait partie d'une série et je vous invite à lire le précédent sur l'utilisation des access keys.

Happy coding !

Pour aller plus loin.

Photo de profil

Ces billets pourraient aussi vous intéresser

Vous nous direz ?!

Commentaires

comments powered by Disqus