My customer recently had a need to securely call an HTTP trigger on an Azure Function remotely from an arbitrary client web application. In this scenario securely meant ensuring that the user has logged into Azure Active Directory (AAD), but any number of authentication providers could be used. The SharePoint Patterns and Practices (PnP) team had posted a video (SharePoint PnP Webcast – Calling external APIs securely from SharePoint Framework) that used the SharePoint Framework but my team needed to do this from vanilla JavaScript. Many thanks to the PnP team and my peer Srinivas Varukala for their inspiration and code samples.
Overview
The key components to this solution involve the following:
- Azure AD app registration (used to enforce authentication on Azure Function)
- Azure Function configured to enforce Azure AD authentication
- Client web application with JavaScript code to call the Azure Function
Azure Function
In the Azure Portal create a new Azure Function. Choose an HTTP Trigger and use the language of choice (I’m using C# script in this example). The Azure Function will validate if a claims principal exists on the incoming request and then output to the logs the name of the user if authenticated.
<Update 2018-05-02>
Note: the Azure portal currently does not support the headers required for CORS (cross-origin resource sharing) requests that contain credentials. Feedback (source) has been provided to the Azure App Service team to support this but was declined. As such the manual processing of CORS requests is not supported at this time. You will need to determine if this workaround works for you or not.
</Update 2018-05-02>
<Update 2019-06-25>
Thanks to comment from Michael Armstrong who pointed out that CORS with credential headers is now supported in Azure App Service. I have validated that this will work with the updated configuration detailed in the post now.
</Update 2019-06-25>
using System.Net; using System.Security.Claims; using System.Threading; public static HttpResponseMessage Run(HttpRequestMessage req, TraceWriter log) { log.Info("C# HTTP trigger function processed a request."); // check for authenticated user on incoming request if (!ClaimsPrincipal.Current.Identity.IsAuthenticated) { log.Info("Claims: Not authenticated"); } else { log.Info("Claims: Authenticated as " + ClaimsPrincipal.Current.Identity.Name); } var resp = req.CreateResponse(HttpStatusCode.OK, "Hello there " + ClaimsPrincipal.Current.Identity.Name); // manually process CORS request (no longer needed with //if (req.Headers.Contains("Origin")) //{ // var origin = req.Headers.GetValues("origin").FirstOrDefault(); // resp.Headers.Add("Access-Control-Allow-Credentials", "true"); // resp.Headers.Add("Access-Control-Allow-Origin", origin); // resp.Headers.Add("Access-Control-Allow-Methods", "GET, POST, OPTIONS"); // resp.Headers.Add("Access-Control-Allow-Headers", "Content-Type, Set-Cookie"); //} return resp; }
Azure AD App
In order to enforce Azure AD authentication on the Azure Function an Azure AD app registration needs to be created. Log into the Azure AD admin portal. Under Azure Active Directory –> App Registrations create a new app registration.
Take note of the Application ID (also known as client ID) for the application created. This will be used later in the Azure App Service authentication / authorization configuration.
The default permissions for the Azure AD app registration (delegated: sign in and read user profile) will be sufficient.
Enforce authentication
Return to the Azure Function and navigate to the Platform features –> Authentication / Authorization screen. Turn App Service Authentication to On, set “Action to take…” to “Log in with Azure Active Directory”, then click the Azure Active Directory authentication provider to configure it as follows.
Fill in the Application ID / Client ID from the previously created Azure AD app registration. Specify the IssuerUrl of the Azure AD domain (typically https://login.microsoftonline.com/TENANT_NAME_GOES_HERE.onmicrosoft.com).
Remove CORS configuration
As noted previously, the Azure portal currently (as of writing May 1, 2018) does not support Azure App Service processing CORS requests that contain credentials. As such removing all domains from the CORS configuration in Azure Portal is unsupported. Please validate if this workaround works for you or not.
<Update 2019-06-25>
Update CORS configuration
Now that Azure App Service supports processing CORS requests that contain credentials, update the CORS settings with the allowed origins that will be calling into your App Service (Azure Function).
Note that the values in below screenshot are using values from running my sample app in debug mode. You’ll want to use the published location for a production environment.
</Update 2019-06-25>
Client code
In this example I started with a .Net Framework MVC project from Visual Studio 2017 v15.6.7 but the code could be hosted on any page with HTML, JavaScript, and a logged in user to Azure AD. Note that the MVC project allows enforcing Azure AD authentication which is what I was most interested in.
HTML snippet to include inside of an IFRAME element with source pointing to root of Azure Function.
<Update 2019-04-11> After the IFRAME has loaded and authenticated you should see a cookie tied to the domain hosting the Azure Function. See following for successful setting of the auth cookie.
If this cookie is not set (ex. cookie size is too large, request / response headers deny the cookie, cookies not allowed by policy, etc.) the outgoing AJAX request will not be able to satisfy the authentication requirement which will result in an error. See following for sample issues that could be encountered when setting the auth cookie.
</Update 2019-04-11>
JavaScript snippet to call HTTP trigger of Azure Function.
Note that in a production scenario you would want to ensure that the IFRAME has loaded fully (and thus authentication cookie set) prior to the Azure Function being called.
$(document).ready(function () { $("#btnExternalCall").click(function (e) { var serviceURL = "https://NAME_OF_AZURE_FUNCTION_GOES_HERE.azurewebsites.net/api/HttpTriggerCSharp1"; $.ajax({ url: serviceURL, type: "GET", xhrFields: { withCredentials: true }, crossDomain: true, success: function (data) { alert("Success: " + data); }, error: function (ex) { alert("Failure getting user token"); } }); }); });
Testing
When all has been configured you can test scenario. If you enter F12 developer tools from your browser of choice you should see the authentication cookie for both the client web application as well as the Azure Function domains.
After issuing the call to the HTTP Trigger on the Azure Function you should see that the call was indeed authenticated and the ClaimsPrincipal is returned.
Conclusion
This is a very powerful capability being able to ensure that an HTTP trigger on an Azure Function only allows authenticated users to call the endpoint. Hopefully this post helps others who have a similar need. Please leave and questions or feedback in the comments below.
-Frog Out
Hello. Please will this work also on SharePoint online. So the JS code will be not in MVC application by directly in SharePoint Online script editor webpat… Thank you
LikeLike
Vladimir, I haven’t tested on SharePoint Online. There might be cross domain restrictions but can always test it out yourself to verify.
LikeLike
Hello. Yes yes I have tested this and there is some problem …. I have created stackowerflow question: https://stackoverflow.com/questions/53327379/javascript-call-of-azure-function-with-ad-authentification-from-sharepoint-onlin
I am not sure why I get these messages…without AD authentification azure function work whit doesn’t….Thank you for your help
LikeLike
Hello. I found the problem….I didn’t have GET but POST…So the problem was there…Right now everything is working…But what about the POST…. Why this solution doesn’t work with POST. Thank you
LikeLike
https://azure.microsoft.com/en-us/blog/simplifying-security-for-serverless-and-web-apps-with-azure-functions-and-app-service/
Azure portal now supports Azure App Service processing CORS requests that contain credentials. 🙂
LikeLiked by 1 person
Michael, thanks for linking to updated information. Will update article next week when back in office.
LikeLike
No problem!
LikeLike
Michael, blog post updated with new info, screenshot, and code to reflect CORS support for credentialed requests. Thanks again.
LikeLike
Hi Brian, me again. Did you run into any issues after the function had been running for 60 minutes? Currently, I’m getting the following error after 60 minutes of running.
error invalid_grant error_description AADSTS500133: Assertion is not within its valid time range.
LikeLike
Michael,
May I ask if you are running your application as a single page application (SPA) or something else?
It sounds like the authorization token is only valid for 1 hr. You likely need to get a new token since the previous one is expiring. Libraries such as ADAL and MSAL will leverage a token cache until the token is within 5 mins of expiration before they fetch a new one. I’m not sure how this would work given the IFRAME is how you are “fetching” an auth token. You’ll want to resubmit the request to the IFRAME target after ~55 mins if that is possible.
LikeLike
[…] Calling Azure AD Secured Azure Function Externally From JavaScript […]
LikeLike
[…] Azure AD authentication in Azure Functions Calling Azure AD Secured Azure Function Externally From JavaScript […]
LikeLike
Hi Brian,
Thanks a lot for your article, that helps me lot. I have tried your JavaScript snippet inside a React JS Application and that works. I would like just to ask you how can I get the user token in case I am getting the “Failure getting user token” error
LikeLike
Kiki,
Unfortunately I am not well versed in React. I would start by taking a look at the React “Getting Started” samples for Microsoft Graph. There is a quick start with ready made application or a follow along tutorial that implements similar authentication flow. Hopefully one of these will be helpful. Let me know if not.
“Getting Started” landing page for React w/ Microsoft Graph
https://developer.microsoft.com/en-us/graph/get-started/react
Tutorial for React application (specific page with authentication implementation)
https://docs.microsoft.com/en-us/graph/tutorials/react?tutorial-step=3
LikeLike