J'ai créé une nouvelle application de fonction, activé l'authentification/autorisation App Service ("tilisez l'authentification/autorisation pour protéger votre application et travailler avec des données par utilisateur") et désactivé les demandes non authentifiées.
Jusqu'à présent, tout semble fonctionner correctement. Si j'essaie de demander ma fonction HttpTrigger
ed, il me faut d'abord me connecter; une fois connecté, toutes les demandes sont traitées comme il se doit. Il n'y a donc aucun problème avec la partie "protéger votre application".
Cependant, je suis totalement coincé avec la partie "travailler avec des données par utilisateur". Ma fonction Azure est appelée en tant que
public static async Task<HttpResponseMessage> Run([HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)]HttpRequestMessage req, TraceWriter log)
Et il n'y a rien de lié à l'authentification dans HttpRequestMessage
. (AuthorizationLevel.Anonymous semble contrôler la toute autre chose - à savoir, si la fonction peut être appelée par n'importe qui ou uniquement par ceux qui ont une clé API fixe).
Comment obtenir l'identité de l'utilisateur authentifié qui a appelé la fonction?
À l'aide de Azure Function runtime v2.0.12309 , vous pouvez récupérer l'instance informations utilisateur authentifié à partir de l'instance ClaimsPrincipal injectée dans le Run
méthode:
public static async Task<HttpResponseMessage> Run(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)]
HttpRequest httpRequest,
ILogger logger,
ClaimsPrincipal claimsPrincipal)
{
// Explores the authenticated user's claims in claimsPrincipal.
}
Il semble que l'on puisse obtenir le nom d'utilisateur actuel à partir de l'état global System.Security.Claims.ClaimsPrincipal.Current.Identity.Name
(Je ne le savais pas lorsque j'ai publié cette question à l'origine). Cependant, il n'est pas clair s'il s'agit d'une méthode fiable ou recommandée pour se connecter aux informations utilisateur.
Exemple:
using System.Net;
using System.Net.Http;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.Azure.WebJobs.Host;
namespace FunctionApp
{
public static class Function1
{
[FunctionName("HttpTriggerCSharp")]
public static async Task<HttpResponseMessage> Run([HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)]HttpRequestMessage req, TraceWriter log)
{
return req.CreateResponse(HttpStatusCode.OK, "Hello " + ClaimsPrincipal.Current.Identity.Name);
}
}
}
Copié de mon problème ici, qui était destiné à trouver comment déboguer localement avec un utilisateur authentifié Azure Active Directory.
https://stackoverflow.com/a/49402152/3602057
Cela fonctionnera avec tout fournisseur configuré sur Azure avec votre fonction Azure, et fonctionnera à la fois pour le débogage local et les scénarios déployés.
Bon après quelques heures de plus (OK, BEAUCOUP), j'ai trouvé une solution pour l'instant. Cela fonctionne dans les scénarios locaux et déployés. J'ai posté une solution de modèle ici:
https://github.com/Mike-EEE/Stash/tree/master/AzureV2Authentication/AzureV2Authentication
Voici les étapes qui décrivent le processus global:
function-name
. azurewebsites.netlocal.settings.json
et collez la valeur de l'étape précédente dans le paramètre AuthenticationToken
.AuthenticationBaseAddress
Voici l'événement principal:
public static class AuthenticationExtensions
{
public static Authentication Authenticate(this HttpRequest @this)
{
var handler = new HttpClientHandler();
var client = new HttpClient(handler) // Will want to make this a singleton. Do not use in production environment.
{
BaseAddress = new Uri(Environment.GetEnvironmentVariable("AuthenticationBaseAddress") ?? new Uri(@this.GetDisplayUrl()).GetLeftPart(UriPartial.Authority))
};
handler.CookieContainer.Add(client.BaseAddress, new Cookie("AppServiceAuthSession", @this.Cookies["AppServiceAuthSession"] ?? Environment.GetEnvironmentVariable("AuthenticationToken")));
var service = RestService.For<IAuthentication>(client);
var result = service.GetCurrentAuthentication().Result.SingleOrDefault();
return result;
}
}
Notez que:
HttpClient
est créé pour chaque appel. Ceci est contraire aux meilleures pratiques.Voici les classes d'intérêt restantes, par souci d'exhaustivité:
public class Authentication // structure based on sample here: https://cgillum.tech/2016/03/07/app-service-token-store/
{
[JsonProperty("access_token", NullValueHandling = NullValueHandling.Ignore)]
public string AccessToken { get; set; }
[JsonProperty("provider_name", NullValueHandling = NullValueHandling.Ignore)]
public string ProviderName { get; set; }
[JsonProperty("user_id", NullValueHandling = NullValueHandling.Ignore)]
public string UserId { get; set; }
[JsonProperty("user_claims", NullValueHandling = NullValueHandling.Ignore)]
public AuthenticationClaim[] UserClaims { get; set; }
[JsonProperty("access_token_secret", NullValueHandling = NullValueHandling.Ignore)]
public string AccessTokenSecret { get; set; }
[JsonProperty("authentication_token", NullValueHandling = NullValueHandling.Ignore)]
public string AuthenticationToken { get; set; }
[JsonProperty("expires_on", NullValueHandling = NullValueHandling.Ignore)]
public string ExpiresOn { get; set; }
[JsonProperty("id_token", NullValueHandling = NullValueHandling.Ignore)]
public string IdToken { get; set; }
[JsonProperty("refresh_token", NullValueHandling = NullValueHandling.Ignore)]
public string RefreshToken { get; set; }
}
public class AuthenticationClaim
{
[JsonProperty("typ")]
public string Type { get; set; }
[JsonProperty("val")]
public string Value { get; set; }
}
interface IAuthentication
{
[Get("/.auth/me")]
Task<Authentication[]> GetCurrentAuthentication();
}
public static class Function1
{
[FunctionName("Function1")]
public static IActionResult Run([HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)]HttpRequest req, TraceWriter log)
{
log.Info("C# HTTP trigger function processed a request.");
var authentication = req.Authenticate();
return authentication != null
? (ActionResult)new OkObjectResult($"Hello, {authentication.UserId}")
: new BadRequestObjectResult("Authentication not found. :(");
}
}
Logiquement, AuthorizationLevel.Anonymous ne vous donnera pas un principal de réclamation actuel .
Malheureusement, le simple fait de passer à AuthorizationLevel.Function n'aide pas non plus: pour moi, je trouve que ClaimsPrincipal.Current est nul, même après une authentification réussie via Azure Active Directory B2C.
Enfin, j'ai essayé AuthorizationLevel.User, mais ce n'est pas actuellement pris en charge par les fonctions Azure voir ici
Je crois que vous devez suivre les étapes selon la réponse acceptée sur cette SO question (je suis en train de l'essayer maintenant ...)
L'intégration de première classe avec App Service EasyAuth n'est pas encore en place, mais nous suivons dans notre référentiel ici . Nous examinons cette question maintenant et nous aurons probablement des améliorations dans ce domaine très bientôt.
Comme vous l'avez découvert, vous pouvez activer EasyAuth et il nécessitera une connexion et acheminera le principal authentifié, vous permettant d'y accéder via des API .NET standard comme ClaimsPrincipal.Current, etc. Cependant, le problème est que vous avez dû marquer votre méthode avec un niveau d'authentification anonyme qui n'est pas ce que vous voulez et vous oblige à rejeter les demandes non authentifiées dans votre méthode (voir les notes en question référencées ci-dessus).
Nous allons bientôt résoudre tous ces problèmes dans une prochaine version.