Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Web API / OWIN, SignalR & Authorization

I am developing a prototype of an AngularJS, Web API, SignalR application as a potential starting point for a new project in VS 2013.

At this stage, I'm pretty much using the canned code that visual studio generates for Individual User Accounts.

There's a line in the StartUp.Auth.cs code that looks like this.

app.UseOAuthBearerTokens(OAuthOptions); 

With this in place, I can add the [Authorize] attribute to controllers and it works fine.

Incidentally, with angular I was able to add a standard header containing the token in the JavaScript as follows.

$http.defaults.headers.common.Authorization = 'bearer ' + access_token; 

Then I added SignalR to the project.
It supports it's own version of the [Authorize] attribute but there is no way to pass custom headers when using SignalR.
It's a limitation on the browser side.
The documentation said you could pass the token as part of the query string.
I added that code on the JavaScript side. My SignalR code now included this.
I passed the token as 'bearer_token'.

this.connection = $.hubConnection("/TestHub", { useDefaultPath: false, qs: "bearer_token=" + token }); 

So my issue was how to make OWIN recognize the token now it was no longer in the header.
After a number of searches, I ended up adding code that moved the token from the querystring into the header.
For my prototype, I just added a little code above the original line in StartUp.Auth.cs.

So, now it looked like this:

app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions() {     Provider = new OAuthBearerAuthenticationProvider()     {         OnRequestToken = context =>         {             if (context.Request.Path.Value.StartsWith("/TestHub"))             {                 string bearerToken = context.Request.Query.Get("bearer_token");                 if (bearerToken != null)                 {                     string[] authorization = new string[] { "bearer " + bearerToken };                     context.Request.Headers.Add("Authorization", authorization);                 }             }              return Task.FromResult(context);         }     } });   // Enable the application to use bearer tokens to authenticate users app.UseOAuthBearerTokens(OAuthOptions); 

The code above is rough but this is a prototype so really I just wanted to see if it worked which it did.

Finally getting to the question: Is this the right pattern for integrating bearer token authorization with SignalR and the OWIN pipeline.
I couldn't seem to find much good information on the right way to do this.

like image 758
Peter Trenery Avatar asked Apr 10 '14 13:04

Peter Trenery


People also ask

Can SignalR be used in Web API?

Objective: Use SignalR for notification between Web API, and TypeScript/JavaScript based Web App, where Web API and the Web App is hosted in different domain. Enabling SignalR and CORS on Web API: Create a standard Web API project, and install the following NuGet packages: Microsoft.

What is the difference between SignalR and WebSockets?

WebSockets is actually the underlying transport that SignalR uses, at least most of the time. SignalR has the ability to fall back to other ways of transporting messages, such as long-polling over HTTP. This is useful in situations where you don't have WebSockets support.

Is SignalR an API?

Azure SignalR Service provides REST API to support server to client communication scenarios, such as broadcasting. You can choose any programming language that can make REST API call. You can post messages to all connected clients, a specific client by name, or a group of clients.


2 Answers

I use a class like this:

public class OAuthTokenProvider : OAuthBearerAuthenticationProvider {     private List<Func<IOwinRequest, string>> _locations;     private readonly Regex _bearerRegex = new Regex("((B|b)earer\\s)");     private const string AuthHeader = "Authorization";      /// <summary>     /// By Default the Token will be searched for on the "Authorization" header.     /// <para> pass additional getters that might return a token string</para>     /// </summary>     /// <param name="locations"></param>     public OAuthTokenProvider(params Func<IOwinRequest, string>[] locations)     {         _locations = locations.ToList();         //Header is used by default         _locations.Add(x => x.Headers.Get(AuthHeader));     }      public override Task RequestToken(OAuthRequestTokenContext context)     {         var getter = _locations.FirstOrDefault(x => !String.IsNullOrWhiteSpace(x(context.Request)));         if (getter != null)         {             var tokenStr = getter(context.Request);             context.Token = _bearerRegex.Replace(tokenStr, "").Trim();         }         return Task.FromResult<object>(null);     } } 

Which instead of just passing on the token to the header, parses it and sets it on the context.

Then it could be used in your app configuration like this:

app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions {     Provider = new OAuthTokenProvider(          req => req.Query.Get("bearer_token"),          req => req.Query.Get("access_token"),          req => req.Query.Get("token"),          req => req.Headers.Get("X-Token"))     }); 

Then the following styles of requests would have their token un-encrypted, for use with authentication and authorization:

GET https://www.myapp.com/authorized/endpoint?bearer_token=123ABC HTTP/1.1 GET https://www.myapp.com/authorized/endpoint?access_token=123ABC HTTP/1.1 GET https://www.myapp.com/authorized/endpoint?token=123ABC HTTP/1.1  GET https://www.myapp.com/authorized/endpoint HTTP/1.1 X-Token: 123ABC  GET https://www.myapp.com/authorized/endpoint HTTP/1.1 Authorization: 123ABC 
like image 199
calebboyd Avatar answered Sep 21 '22 06:09

calebboyd


This how I solved it

var authData = localStorageService.get('authorizationData'); var token = authData.token;  $.signalR.ajaxDefaults.headers = { Authorization: "Bearer " + token }; 

No change on the server side code

Hope it helps someone

like image 40
Santiago Robledo Avatar answered Sep 20 '22 06:09

Santiago Robledo