Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

WebAPI & SignalR web application - Authenticating & Authorizing

I have a WebAPI solution and I use token authentication, so the flow is the following:

  • user tries to login using the username and password
  • if the credentials are correct, he is given a token in order to use in the following requests (to be placed in the header of the AJAX requests).

Now SignalR comes into play. Since it uses WebSockets, you are not able to pass a header. Searching for a solution, I've come across the following:

  1. Actually pass a header to SignalR - not an option since it forces SignalR to use longPolling.

$.signalR.ajaxDefaults.headers = { Authorization: "Bearer " + token };

  1. Pass the same token used for authenticating WebAPI calls as a query string/or store it in a cookie for SignalR, then create a provider that somehow unwraps and expands the token into identity. I followed this blog post but I seem to be missing something.

  2. Again pass the token as a query string/or as a cookie, but this time create a custom Authorize Attribute to Authorize SignalR hubs or methods. Again a blog post about this. The problem with this solution was in Unprotect method on the token.

  3. The final and the easiest solution is to also enable cookie authentication, keep using bearer token authentication for WebAPI calls and let the OWIN Middleware authorize calls on the hubs. (this solution actually works).

Now, the issue is that using the default template for a WebAPI application with Individual User Accounts (so with token authentication), whenever I send an AJAX request to the API it also sends the cookie.

public partial class Startup
    {
        public static OAuthAuthorizationServerOptions OAuthOptions { get; private set; }

        public static string PublicClientId { get; private set; }

        // For more information on configuring authentication, please visit http://go.microsoft.com/fwlink/?LinkId=301864
        public void ConfigureAuth(IAppBuilder app)
        {
            // Configure the db context and user manager to use a single instance per request
            app.CreatePerOwinContext(ApplicationDbContext.Create);
            app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);

            app.UseCookieAuthentication(new CookieAuthenticationOptions());


            PublicClientId = "self";
            OAuthOptions = new OAuthAuthorizationServerOptions
            {
                TokenEndpointPath = new PathString("/Token"),
                Provider = new ApplicationOAuthProvider(PublicClientId),
                AuthorizeEndpointPath = new PathString("/api/Account/ExternalLogin"),
                AccessTokenExpireTimeSpan = TimeSpan.FromDays(14),
                AllowInsecureHttp = true
            };

            app.UseOAuthBearerTokens(OAuthOptions);

        }
    }



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(OAuthDefaults.AuthenticationType));

        // Web API routes
        config.MapHttpAttributeRoutes();

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

Even if I did this:

    config.SuppressDefaultHostAuthentication();
    config.Filters.Add(new HostAuthenticationFilter(OAuthDefaults.AuthenticationType));

Authorization: Bearer 2bTw5d8Vf4sKR9MNMqZsxIOPHp5qtXRTny5YEC_y7yWyrDLU0__q8U8Sbo7N7XBjPmxZXP18GRXjDVb3yQ9vpQnWXppRhVA8KDeGg2G5kITMxiOKvGMaKwyUGpORIeZ0UHyP9jA2fX9zPwzsCqHmq-LoGKls0MQNFjXgRGCCCvro5WPMAJcLs0kUoD_2W_TOTy9_T-koobw-DOivnazPo2Z-6kfXaIUuZ1YKdAbcSJKzpyPR_XrCt4Ma2fCf-LcpMPGo4gDFKfxWdId0XtfS9S-5cXmmOmGM4Y6MkAUK8O9sZlVrpmpvV0hjXF2QwfLtQViPyEctbTr1vPBNn014n60APwGSGnbUJBWMvJhqcjI5pWoubCmk7OHJrn052U_F3bDOi2ha1mVjvhVY1XMAuv2c3Pbyng2ZT_VuIQI7HjP4SLzV6JjRctfIPLEh67-DFp585sJkqgfSyM6h_vR2gPA5hDocaFs73Qa22QMaLRrHThU0HM8L3O8HgFl5oJtD
Referer: http://localhost:15379/index.html
Accept-Encoding: gzip, deflate, sdch
Accept-Language: en-US,en;q=0.8,ro;q=0.6
Cookie: .AspNet.Cookies=E71BnnTMv8JJ4hS9K46Y2yIbGMQCTS4MVBWBXezUYCSGXPbUPNZh98Q0IElQ0zqGyhB7OpYfdh10Kcy2i5GrWGSiALPPtOZUmszfAYrLZwG2JYiU5MSW80OGZVMY3uG2U1aqvvKJpv7eJwJSOoS4meD_3Qy8SwRzTg8feZArAE-REEXSsbPfq4jQBUUbxfDAyuPVRsLNfkn4oIAwZTs85IulRZI5mLnLqOS7VLejMGIWhkuyOWvvISu1pjsP5FMDXNwDkjv2XCaOpRzZYUxBQJzkcdpDjwW_VO2l7HA263NaG_IBqYpLqG57Fi-Lpp1t5Deh2IRB0VuTqAgrkwxifoBDCCWuY9gNz-vNjsCk4kZc8QKxf7el1gu9l38Ouw6K1EZ9y2j6CGWmW1q-DobaK9JXOQEPm_LGyaGPM5to2vchTyjuieZvLBAjxhLKnXdy34Z7MZXLVIwmpSmyPvmbIuH9QzOvTWD-I1AQFJyCDw8

Do you see an easier way of authenticating SignalR with token authentication? Is this final approach (if I manage to suppress the sending of the cookie with requests) viable in production?

like image 928
radu-matei Avatar asked Sep 04 '15 11:09

radu-matei


1 Answers

When working on that particular project, I ended up simply using cookie authentication and CSRF, but I was still interested in seeing how to authenticate WebApi and SignalR using bearer token authentication.

For a working sample, please see this GitHub repository.

I ended up creating an OAuthBearerTokenAuthenticationProvider class that tries to retrieve the token from the cookie.

public class OAuthBearerTokenAuthenticationProvider : OAuthBearerAuthenticationProvider
    {
        public override Task RequestToken(OAuthRequestTokenContext context)
        {
            var tokenCookie = context.OwinContext.Request.Cookies["BearerToken"];

            if (!String.IsNullOrEmpty(tokenCookie))
                context.Token = tokenCookie;

            return Task.FromResult<object>(null);
        }
    }

And here is the method in the Startup class that deals with authentication:

    public void ConfigureOAuth(IAppBuilder app)
    {
        OAuthAuthorizationServerOptions OAuthServerOptions = new OAuthAuthorizationServerOptions()
        {
            AllowInsecureHttp = true,
            TokenEndpointPath = new PathString("/token"),
            AccessTokenExpireTimeSpan = TimeSpan.FromDays(1),
            Provider = new AuthorizationServerProvider()
        };

        app.UseOAuthAuthorizationServer(OAuthServerOptions);
        app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions()
        {
            Provider = new OAuthBearerTokenAuthenticationProvider()
        });

    }

For a working sample, please see this GitHub repository.

Hope this helps someone at some point.

like image 94
radu-matei Avatar answered Oct 20 '22 08:10

radu-matei