Scenario:
I have two ASP.NET web applications hosted separately in Windows Azure and both associated with the same Azure Active Directory tenant:
An MVC application with the AngularJS SPA interface and adal.js library for processing Azure AD authentication on the client.
Web API with Microsoft OWIN middleware for processing Azure AD authentication on a server.
Problem:
When angular loads the client application, the page loads correctly after redirecting oauth to the corresponding Identity Authority, and the adal.js library correctly extracts and saves different tokens for each application (checked by checking resources / Session Holding tab in Chrome dev tools). But when the client application tries to access or update some data in the API, CORS pre-check requests respond to 302 redirects in the Identity Authority, which leads to the following error in the console:
XMLHttpRequest cannot load https://webapi.azurewebsites.net/api/items . The request was redirected to ' https://login.windows.net/ {authority-guid} / oauth2 / authorize? response_type = id_token & redirect_uri = .... etc..etc .. ", which is prohibited for cross-origin requests requiring preflight protection.
Examples of headers (anonymous):
Request
OPTIONS / api / items HTTP / 1.1
Host: webapi.azurewebsites.net
Connection: keep-alive
Access-Control-Request-Method: GET
Access-Control-Request-Headers: accept, authorization
Origin: https://mvcapp.azurewebsites.net
User-Agent: Mozilla / 5.0 (Windows NT 6.3; WOW64) AppleWebKit / 537.36 (KHTML, like Gecko) Chrome / 39.0.2171.99 Safari / 537.36
Accept: * / *
Referer: https://mvcapp.azurewebsites.net/
Response
HTTP / 1.1 302 Found
Content-Length: 504
Location: https://login.windows.net/{authority-guidasket/oauth2/authorize?response_type=id_token&redirect_uri=https%3A%2F%2F....etc..etc.%2F&client_id={api-guid} & scope = openid + profile + email & response_mode = form_post & state = ... etc ...
Server: Microsoft-IIS / 8.0
X-Powered-By: ASP.NET
Set-Cookie: ARRAffinity = 4f51 ... snip .... redact .... db6d; Path = /; Domain = webapi.azurewebsites.net What i did / tried
- Ensure that your Azure AD tenant allows you to use the implicit OAuth2 flow, as described here and elsewhere.
- Ensure that the API provides access permissions and that MVC / SPA are registered for access using the permissions granted.
- Explicitly added the OPTIONS verb handler to the web.config API (see below).
- Various combinations of enabling CORS are used on the API server, OWIN, as well as with EnableCorsAttribute (see below).
Questions
Can I somehow associate the web API with the Azure AD agent with redirection to CORS pre-validation requests? Is there any missing initialization in the adal.js library and / or OWIN startup code (see below)? Are there any settings on the Azure portal that allow OPTIONS requests through the OWIN pipeline?
Relevant Code:
adal.js initialization
angular.module("myApp", ["ngRoute", "AdalAngular"]) .config(["$routeProvider", "$locationProvider", "$httpProvider", "adalAuthenticationServiceProvider", function ($routeProvider, $locationProvider, $httpProvider, adalProvider) { $routeProvider.when("/", { // other routes omitted for brevity templateUrl: "/content/views/home.html", requireADLogin: true // restrict to validated users in the Azure AD tenant }); // CORS support (I've tried with and without this line) $httpProvider.defaults.withCredentials = true; adalProvider.init({ tenant: "contoso.onmicrosoft.com", clientId: "11111111-aaaa-2222-bbbb-3333cccc4444", // Azure id of the web app endpoints: { // URL and Azure id of the web api "https://webapi.azurewebsites.net/": "99999999-zzzz-8888-yyyy-7777xxxx6666" } }, $httpProvider); } ]);
Initializing OWIN Middleware
public void ConfigureAuth(IAppBuilder app) {
WebApiConfig Initialization
public static void Register(HttpConfiguration config) {
API OPTIONS register verb handler
<system.webServer> <handlers> <remove name="ExtensionlessUrlHandler-Integrated-4.0" /> <remove name="OPTIONSVerbHandler" /> <remove name="TRACEVerbHandler" /> <add name="OPTIONSHandler" path="*" verb="OPTIONS" modules="IsapiModule" scriptProcessor="C:\Windows\Microsoft.NET\Framework64\v4.0.30319\aspnet_isapi.dll" /> <add name="ExtensionlessUrlHandler-Integrated-4.0" path="*." verb="*" type="System.Web.Handlers.TransferRequestHandler" preCondition="integratedMode,runtimeVersionv4.0" /> </handlers> </system.webServer>
Related Resources
At one point or another, I tried almost all conceivable combinations of things from the following (and many other) forum and blog posts and github example code.