Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

AngularJS and OWIN Authentication on WebApi

I have implemented OWIN token based authentication on my WebApi, I have also enabled CORS by calling app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll)

I can access various unsecured portions of my app from an angularjs web client. I have used this http-interceptor , when I try to access a protected resource, I get my login pop.

Now in order to login I have to call http://mywebapi/token with form encoded UserName Password and grant_type, see my header signature below (from chrome)

Request URL:http://mywebapi/token
Request Headers CAUTION: Provisional headers are shown.
Accept:application/json, text/plain, */*
cache:false
Content-Type:application/x-www-form-urlencoded
Origin:http://127.0.0.1:49408
Referer:http://127.0.0.1:49408/
User-Agent:Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.153 Safari/537.36
Form Dataview sourceview URL encoded
grant_type:password
UserName:correctuser
Password:Password

When I use postman to send this request, it comes back fine with the expected accesstoken, however when I try to use angular's $http service, it makes the OPTIONS request (I can see this in Dev tools console) but for some reason I get this error message

No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://127.0.0.1:49408' is therefore not allowed access.

NOTE: This only happens for the /token request which is form-url-encoded, for all other json requests the header is added as expected. Can someone please help, I am running out of ideas.

Thanks

like image 230
Obi Avatar asked Jun 27 '14 22:06

Obi


4 Answers

For those curious about the answer and the previous answer, it is indeed strongly related the ordering. Whenever you are adding Owin middleware it is important to note: The order of registration is imperative.

app.useCors(Microsoft.Owin.Cors.CorsOptions.AllowAll)

Having this as the first thing in your auth file, basically registers the Cors handler to occur prior to reaching your OAuthServer and Web Api.

Moving it after the OAuth does the opposite, hence the need to add the Access-Control-Allow-Origin header in the GrantResourceOwnerCredentials.

To answer the other question, the reason the header is already there is if you send a CORS request from the browser and the CorsOptions.AllowAll is specified, it adds one for you so by the time it reaches the /token endpoint on the OAuth server it has already added one. (just means that one was found in the http request and you are allowing all origins).

You can verify the behaviours accordingly,

In Fiddler, send a new request to your Token endpoint with an Origin header included with an arbitrary value. Put a breakpoint on your OAuth server in the GrantResourceOwnerCredentials and then examine context.Response.Headers, it will now contain the origin you passed in. (Remember, the browser must examine it, fiddler will be happy all day long)

If you then tell CORS not to use CorsOptions.AllowAll and set AllowAnyOrigin to false you will notice that the Origin sent from Fiddler is no longer added to the response headers.

The browser in turn will deny the CORS request because the origin was not returned - Origin "" not found in Access-Control-Allow-Origin header.

NOW FOR THE IMPORTANT BIT:

If you set CorsOptions.AllowAll it does exactly what it says it does, allows CORS requests to any method on any middleware that occurs after the CORS registration in the Owin pipeline so make sure that is what you intend to do. IE: If you register CORS first then OAuth and Web API then all your Web API methods will be accessible via CORS if you do not explicitly add code\attributes to prevent it.

If you want to restrict the methods then implement an ICorsPolicyProvider, some portions from http://katanaproject.codeplex.com/(Microsoft.Owin.Cors)

      public class MyCorsPolicyProvider : ICorsPolicyProvider
        {
            public Task<CorsPolicy> GetCorsPolicyAsync(IOwinRequest request)
            {
                // Grant Nothing.
                var policy = new CorsPolicy
                {
                    AllowAnyHeader = false,
                    AllowAnyMethod = false,
                    AllowAnyOrigin = false,
                    SupportsCredentials = false
                };

                // Now we can get a bit clever: (Awesome, they requested the token endpoint. Setup OAuth options for that.
                if (OAuthOptions.TokenEndpointPath.HasValue && OAuthOptions.TokenEndpointPath == request.Path)
                {
                    // Hypothetical scenario, tokens can only be obtained using CORS when the Origin is http://localhost
                    policy.AllowAnyHeader = true;
                    policy.AllowAnyMethod = true;
                    policy.AllowAnyOrigin = false;
                    policy.SupportsCredentials = true;
                    policy.Origins.Add("http://localhost");

                return Task.FromResult(policy);
                }
                // No token?, must already have one.... so this must be a WebApi request then.
                // From here we could check where the request is going, do some other fun stuff etc... etc...
                // Alternatively, do nothing, set config.EnableCors() in WebApi, then apply the EnableCors() attribute on your methods to allow it through.
return null;            }
        }

The return null; tells Owin to continue to the next middleware and to allow the request through but with no policy thus NO CORS!, allowing you to set appropriate CORS attributes in WebAPI

Now the really important bit, DO NOT add the Access-Control-Allow-Origins header to your response if it is not there unless that is really what you intend as depending on your middleware registration order it will open all the doors for CORS requests unless you explicitly block them elsewhere or remove the header and basically will cause you lots of issues when you try and use CORS with WebApi and want to restrict it.

To block them elsewhere you could add a CorsPolicyProvider (System.Web.Http) for WebApi then set a Context variable in Owin which you can read once the request hits WebApi.

    public class WebApiCorsPolicyProvider : System.Web.Http.Cors.ICorsPolicyProvider
    {
        public Task<CorsPolicy> GetCorsPolicyAsync(HttpRequestMessage request, CancellationToken cancellationToken)
        {
            var policy = new CorsPolicy
            {
                AllowAnyHeader = false,
                AllowAnyMethod = false,
                AllowAnyOrigin = false,
                SupportsCredentials = false
            };
// The benefit of being at this point in the pipeline is we have been authenticated\authorized so can check all our claims for CORS purposes too if needed and set errors etc...

            // In an Owin pipeline?
            var owinContext = request.GetOwinContext();

            if (owinContext != null)
            {
               // We have an owin pipeline, we can get owin parameters and other things here.
            }
            else
            {
                // Write your code here to determine the right CORS options. Non Owin pipeline variant.   
            }

            return Task.FromResult(policy);
        }
    }

And finally, one other benefit of propagating downwards to a WebApi CORS policy provider is that at that point Authorization will have taken place so you can then apply additional Origin filters at that stage in the CORS policy provider.

like image 159
Sacha Rice Avatar answered Nov 16 '22 01:11

Sacha Rice


In my opinion it is related to ordering of your statements though I did not investigated further. I faced the same issue and tried all combinations and eventually following worked for me.

        public void Configuration(IAppBuilder app)
    {
        HttpConfiguration config = new HttpConfiguration();

        ConfigureOAuth(app);
        WebApiConfig.Register(config);
        app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);

        app.UseWebApi(config);
    }

I was following Token Based Authentication using ASP.NET Web API 2, Owin, and Identity

like image 43
Arvind Dhasmana Avatar answered Sep 30 '22 23:09

Arvind Dhasmana


I went through the same process and spend (wasted?) the same amount of time as most people, dealing with owin + web api.

A solution which worked for me was to move

app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);

before everything else in the pipe.

Here is some code:

OwinStartup

[assembly: OwinStartup(typeof(MyApp.Web.Startup))]
namespace MyApp.Web
{
    using Owin;
    using Microsoft.Owin;

    public partial class Startup
    {
        public void Configuration(IAppBuilder app)
        {
        var config = new System.Web.Http.HttpConfiguration();
        ConfigureAuth(app, config);
        }
    }
}

Startup for OAuth

public partial class Startup
{
    public void ConfigureAuth(IAppBuilder app, System.Web.Http.HttpConfiguration config)
        {
        // app.UseWelcomePage("/");
        // app.UseErrorPage();

        // Must be the first to be set otherwise it won't work.
        app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);

        app.CreatePerOwinContext<ApplicationDatabaseContext>(ApplicationDatabaseContext.Create);
        app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);

        app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());

        var OAuthOptions = new OAuthAuthorizationServerOptions
        {
            AllowInsecureHttp = true,
            TokenEndpointPath = new PathString("/token"),
            AccessTokenExpireTimeSpan = TimeSpan.FromDays(1),
            Provider = new DaufAuthorizationServerProvider(),
            RefreshTokenProvider = new SimpleAuthorizationServerProvider(),
        };
        app.UseOAuthAuthorizationServer(OAuthOptions);

        app.UseWebApi(WebApiConfig.Register(config, logger));
        }
}

Web Api

public static class WebApiConfig
{
    public static HttpConfiguration Register(System.Web.Http.HttpConfiguration config, ILogger logger)
        {
            // Web API configuration and services
            // Configure Web API to use only bearer token authentication.
            // This will used the HTTP header: "Authorization"      Value: "Bearer 1234123412341234asdfasdfasdfasdf"
            config.SuppressDefaultHostAuthentication();
            config.Filters.Add(new HostAuthenticationFilter(OAuthDefaults.AuthenticationType));

            // Web API routes
            config.MapHttpAttributeRoutes();

            config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{id}",
                defaults: new { id = RouteParameter.Optional }
            );

            return (config);
        }
}
like image 17
LeftyX Avatar answered Nov 16 '22 00:11

LeftyX


So I found the answer but brace yourself 'coz this one's weird!! I read this article on code project which led me to my Owin Authorisation server's GrantResourceOwnerCredentials method to check for this

context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "*" });

(Mine is a custom Authoris(z)ation server, one I nicked off here)

The shocking thing I found was that it was already there!

So I decided to set a break point on that line and what do you know, that line was failing because (...even more shocking) "Access-Control-Allow-Origin" was already in the headers!!

So I commented that line out and it all worked! But then the caveat, I have no idea how the header got in, so I have no idea if it will be there or not in production so I swapped that line of code with this to check and then add it if it was not already there

var header = context.OwinContext.Response.Headers.SingleOrDefault(h => h.Key == "Access-Control-Allow-Origin");
            if (header.Equals(default(KeyValuePair<string, string[]>)))
            {
                context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "*" });
            }

I hope my labour of love will save a few souls from the excruciating damnation of countless hours of tinkering with nothing to solve this problem. Cheers!

like image 12
Obi Avatar answered Nov 16 '22 01:11

Obi