I have created a new Function App, enabled App Service Authentication / Authorization for it ("Use Authentication / Authorization to protect your application and work with per-user data") and disabled non-authenticated requests.
Everything seems to be working correctly so far. If I try to request my HttpTrigger
ed function, it requires me to log in first; once I'm logged in, all requests are processed as they should be. So there is no problem with "protect your application" part.
However, I'm totally stuck with the "work with per-user data" part. My Azure Function is invoked as
public static async Task<HttpResponseMessage> Run([HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)]HttpRequestMessage req, TraceWriter log)
And there is nothing related to authentication in HttpRequestMessage
. (AuthorizationLevel.Anonymous seems to control the entirely different thing - namely, if the function could be called by anyone or only by those who have a fixed API key).
How do I get the identity of authenticated user who called the function?
Using the Azure Function runtime v2.0.12309, you can retrieve the authenticated user information from the ClaimsPrincipal instance injected in the Run
method:
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.
}
It seems that one can get current user name from global state System.Security.Claims.ClaimsPrincipal.Current.Identity.Name
(I didn't know that when I originally posted this question). However, it is unclear if this is a reliable or recommended method of getting logged in user info.
Example:
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);
}
}
}
First class integration with App Service EasyAuth is something that isn't in place yet, but we're tracking in our repo here. We're looking at this now and will likely have some improvements in this area very soon.
As you discovered, you can enable EasyAuth and it will require login, and will flow the authenticated principal through, allowing you to access it via standard .NET APIs like ClaimsPrincipal.Current, etc. However the problem is that you had to mark your method with auth level Anonymous which isn't what you want, and requires you to reject unauthenticated requests in your method (see notes in issue referenced above).
We'll iron all these issues out soon in an upcoming release.
Logically, AuthorizationLevel.Anonymous will not give you a current claims principal.
Unfortunately, just changing to AuthorizationLevel.Function doesn't help either: For me I am finding that ClaimsPrincipal.Current is null, even after authenticating successfully via Azure Active Directory B2C.
Finally, I tried AuthorizationLevel.User, but that isn't currently supported by azure functions see here
I believe you need to follow the steps as per the accepted answer on this SO question (I'm in process of trying it now ...)
Copied from my issue here, which was intended to find how to debug locally with an Azure Active Directory authenticated user.
https://stackoverflow.com/a/49402152/3602057
This will work with any provider that is configured on Azure with your Azure Function, and will work while for both local debugging and deployed scenarios.
Alright after a few more (OK, a LOT) hours, I have figured out a solution for now. This works in both local and deployed scenarios. I have posted a template solution here:
https://github.com/Mike-EEE/Stash/tree/master/AzureV2Authentication/AzureV2Authentication
Here are the steps that outline the overall process:
- Sign into your function at https://
function-name
.azurewebsites.net - CTRL-SHIFT-C in Chrome -> Application -> Cookies -> -> AppServiceAuthSession -> Copy Value
- Open
local.settings.json
and paste value from previous step inAuthenticationToken
setting. - While you're there, paste in the URL from first step in
AuthenticationBaseAddress
- Launch application.
- Cross fingers.
- Enjoy magic (Hopefully.)
Here is the main event:
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;
}
}
Note that:
- An
HttpClient
is created for each call. This is against best practices. - Sample code is based on EasyAuth sample by @stuartleeks
- This solution makes use of the excellent Refit project to get its data.
Here are the remaining classes of interest, for the sake of completeness:
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. :(");
}
}
来源:https://stackoverflow.com/questions/44587136/how-to-get-current-user-identity-in-azure-function-with-azure-authentication