radu-matei radu-matei - 21 days ago 29
ASP.NET (C#) Question

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?

Answer

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.

Comments