OWIN and Forms Authentication with WEB API 2 with SPA

11,463

Solution 1

Way too long to post but added all the details on how to set this up on github gist.

Solution 2

Are you using Bearer token from your app? If you didn't use it and just want to use cookie, please remove following code:

        // Web API configuration and services
        // Configure Web API to use only bearer token authentication.
        config.SuppressDefaultHostAuthentication();
        config.Filters.Add(new HostAuthenticationFilter(Startup.OAuthBearerOptions.AuthenticationType));

The code above will only allow bearer authentication for web api.

And you may also remove app.UseOAuthBearerAuthentication(OAuthBearerOptions); to remove bearer authentication middleware from OWIN pipeline.

If you want to use bearer token in your app, you need to set the token before sending ajax request in browser.

Share:
11,463
amcdnl
Author by

amcdnl

obsessed with the web / security / tech follow me on github or twitter: @amcdnl

Updated on July 25, 2022

Comments

  • amcdnl
    amcdnl almost 2 years

    I have a Web API 2 Project that is referenced by a SPA JavaScript application.

    I'm using OWIN to authenticate the requests and upon login with Forms authentication, however, on each send back to the server my resources are not authenticated after I login.

    App_Start/WebApiConfig.cs

    namespace API
    {
        public static class WebApiConfig
        {
            public static void Register(HttpConfiguration config)
            {
                // Web API configuration and services
                // Configure Web API to use only bearer token authentication.
                config.SuppressDefaultHostAuthentication();
                config.Filters.Add(new HostAuthenticationFilter(Startup.OAuthBearerOptions.AuthenticationType));
    
                config.EnableCors(new EnableCorsAttribute(
                    origins: "*", headers: "*", methods: "*"));
    
                // Web API routes
                config.MapHttpAttributeRoutes();
    
                config.Routes.MapHttpRoute(
                    name: "DefaultApi",
                    routeTemplate: "api/{controller}/{id}",
                    defaults: new { id = RouteParameter.Optional }
                );
    
                // Use camel case for JSON data.
                config.Formatters.JsonFormatter.SerializerSettings.ContractResolver =
                    new CamelCasePropertyNamesContractResolver();
            }
        }
    }
    

    /Startup.cs

    [assembly: OwinStartup(typeof(API.Startup))]
    
    namespace API
    {
        public partial class Startup
        {
            public void Configuration(IAppBuilder app)
            {
                ConfigureAuth(app);
            }
        }
    }
    

    App_Start/Startup.Auth.cs

    namespace API
    {
        public partial class Startup
        {
            static Startup()
            {
                OAuthBearerOptions = new OAuthBearerAuthenticationOptions();
            }
    
            public static OAuthBearerAuthenticationOptions OAuthBearerOptions { get; private set; }
    
            public void ConfigureAuth(IAppBuilder app)
            {
                app.UseCookieAuthentication(new CookieAuthenticationOptions());
                app.UseOAuthBearerAuthentication(OAuthBearerOptions);
            }
        }
    }
    

    Controllers/AccountController.cs

    namespace API.Controllers
    {
        public class AccountController : ApiController
        {
    
            public AccountController()
            {
                HttpContext.Current.Response.SuppressFormsAuthenticationRedirect = true;
            }
    
            [HttpPost]
            [AllowAnonymous]
            [Route("api/account/login")]
            [EnableCors(origins: "*", headers: "*", methods: "*", SupportsCredentials = true)]
            public HttpResponseMessage Login(LoginBindingModel login)
            {
                var authenticated = false;
                if (authenticated || (login.UserName == "a" && login.Password == "a"))
                {
                    var identity = new ClaimsIdentity(Startup.OAuthBearerOptions.AuthenticationType);
                    identity.AddClaim(new Claim(ClaimTypes.Name, login.UserName));
    
                    AuthenticationTicket ticket = new AuthenticationTicket(identity, new AuthenticationProperties());
                    var currentUtc = new SystemClock().UtcNow;
                    ticket.Properties.IssuedUtc = currentUtc;
                    ticket.Properties.ExpiresUtc = currentUtc.Add(TimeSpan.FromMinutes(30));
    
                    var token = Startup.OAuthBearerOptions.AccessTokenFormat.Protect(ticket);
                    var response = new HttpResponseMessage(HttpStatusCode.OK)
                    {
                        Content = new ObjectContent<object>(new  
                        { 
                            UserName = login.UserName,
                            AccessToken = token
                        }, Configuration.Formatters.JsonFormatter)
                    };
    
                    FormsAuthentication.SetAuthCookie(login.UserName, true);
    
                    return response;
                }
    
                return new HttpResponseMessage(HttpStatusCode.BadRequest);
            }
    
            [HttpGet]
            [Route("api/account/profile")]
            [Authorize]
            public HttpResponseMessage Profile()
            {
                return new HttpResponseMessage(HttpStatusCode.OK)
                {
                    Content = new ObjectContent<object>(new
                    {
                        UserName = User.Identity.Name
                    }, Configuration.Formatters.JsonFormatter)
                };
            }
        }
    }
    

    Then I invoke it with JavaScript like:

           $httpProvider.defaults.withCredentials = true;
    
           login: function(user, success, error) {
                return $http.post('/api/account/login', user);
            },
    
            profile:function(){
                return $http.get('/api/account/profile');
            }
    

    My cookies are set on the browser:

    ASPXAUTH 040E3B4141C86457CC0C6A10781CA1EFFF1A32833563A6E7C0EF1D062ED9AF079811F1600F6573181B04FE3962F36CFF45F183378A3E23179E89D8D009C9E6783E366AF5E4EDEE39926A39E64C76B165

    but after login, further requests are deemed unauthorized...

    Status Code:401 Unauthorized

    I feel like I'm REALLY close just missing one little piece, anyone got any ideas?