I've spent the past 24 hours reading all about how to create Azure Functions and have successfully converted a MVC WebApi over to a new Function App with multiple functions. My problem is that I've not found any clear documentation or tutorials on how to do the most basic of authentication with them.
My scenario is pretty straight forward. Provision users in my AAD, then grant those users access to specific functions. Users on a website will click on UI elements that in turn trigger Javascript that calls my Azure Functions. In the function I need to be able to verify their identity somehow as I'll be passing that along to other functions that interact with a SQL instance.
Can someone please point me at docs, articles, an example, something, that shows how I can achieve this?
For the record I've found in the portal the "Authentication" config for my Function App and have chosen AAD as my Authentication Provider. I've added my Function App to it and have provisioned a few users. I've then wrote the following test function:
[FunctionName("GetThings")] public static HttpResponseMessage Run([HttpTrigger(AuthorizationLevel.User, "GET", Route = null)]HttpRequestMessage req, TraceWriter log) { log.Info("Getting all the things"); var identity = ClaimsPrincipal.Current.Identity; return identity.IsAuthenticated ? req.CreateResponse(HttpStatusCode.Unauthorized, "Not authenticated!") : req.CreateResponse(HttpStatusCode.OK, $"Hi {identity.Name}!"); }
Currently when trying to hit the endpoint directly I get redirected to a login page... so I guess that part is working. How I generate / retrieve user tokens, send them along on the request to the functions, or process them on the server isn't clear to me though.
Help?
Search for and select the Azure Functions: Open in portal command. Select the subscription and function app name to open the function app in the Azure portal. In the function app that was opened in the portal, locate the Platform features tab, select Authentication/Authorization. Turn On App Service Authentication.
Enable Azure Active Directory in your App Service app. Sign in to the Azure portal and navigate to your app. Select Authentication in the menu on the left. Click Add identity provider.
Azure HTTP authorization level, Determines what keys, if any, need to be present on the request in order to invoke the function.
Once the user authenticates with Azure AD you'll be presented an AppServiceAuthSessoin
cookie. It's an opaque cookie but you can exchange it for a readable id token by calling
https://yourFunctionApp.azurewebsites.net/.auth/me
and passing in the opaque cookie as Cookie
header. Moreover, the id_token
you get back is good for use as Bearer
token.
Actually it just looks right to me, i haven't really tested it as a Bearer, so a little caution there.
The mechanism is called Easy Auth, it's easier to Google for that name.
More on the token store here —
https://cgillum.tech/2016/03/07/app-service-token-store/
...which says you can grab the claims just by reading the HTTP headers coming in from the user's browser:
Accessing the Tokens
From within your backend code, accessing these tokens is as easy as reading an HTTP request header. The headers are named like
X-MS-TOKEN-{provider}-{type}
. The possible token header names are listed below:
Azure Active Directory Token Request Headers:
X-MS-TOKEN-AAD-ID-TOKEN X-MS-TOKEN-AAD-ACCESS-TOKEN X-MS-TOKEN-AAD-EXPIRES-ON X-MS-TOKEN-AAD-REFRESH-TOKEN
I actually just found that out right now, so thanks for the question!
My hunch was correct, the id_token
is also good as Bearer:
$ curl -isk https://{funcApp}.azurewebsites.net/api/{someFunc} \ -H "Authorization: Bearer eyJ0eXAiOi....oEU-Q" HTTP/1.1 200 OK Cache-Control: no-cache Server: Microsoft-IIS/8.0 ...
The main difference between the two ways of reading claims (reading headers vs. calling /.auth/me
from the backend with user's Cookie) is the amount of detail you get. There's way more in the latter.
Here's the set of headers you get from Easy Auth for a Twitter authenticated user:
{ "cookie": "AppServiceAuthSession=Lx43...xHDTA==", ... "x-ms-client-principal-name": "evilSnobu", "x-ms-client-principal-id": "35....", "x-ms-client-principal-idp": "twitter", "x-ms-token-twitter-access-token": "35...Dj", "x-ms-token-twitter-access-token-secret": "OK3...Jx", }
and the claims you get by calling /.auth/me
:
{ "access_token": "35...FDj", "access_token_secret": "OK3...sJx", "provider_name": "twitter", "user_claims": [ { "typ": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier", "val": "352660979" }, { "typ": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn", "val": "evilSnobu" }, { "typ": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name", "val": "Safarihat Hacker" }, { "typ": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/webpage", "val": "..." }, { "typ": "urn:twitter:description", "val": "GENIUS. HAVE BRAIN. WILL TRAVEL." }, { "typ": "urn:twitter:location", "val": "" }, { "typ": "urn:twitter:time_zone", "val": "London" }, { "typ": "urn:twitter:lang", "val": "en" }, { "typ": "urn:twitter:verified", "val": "False" }, { "typ": "urn:twitter:profile_image_url_https", "val": "https://pbs.twimg.com/profile_images/867473646876545024/1elebfK1_normal.jpg" } ], "user_id": "evilSnobu" }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With