Background:
I have an ASP.NET WebAPI project. I'm using Bearer Tokens to authenticate my users. Some of my controller actions are marked with [Authorized]
filter. In the client side, the client gets his token by invoking http://foo.bar/Token
and then adding that token as an Authorization
header to its requests.
There is no problem up to this point and everything is working as it should with these settings in my Startup.Auth.cs
class:
OAuthOptions = new OAuthAuthorizationServerOptions
{
TokenEndpointPath = new PathString("/Token"),
Provider = new ApplicationOAuthProvider(PublicClientId),
AuthorizeEndpointPath = new PathString("/api/Account/ExternalLogin"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(14),
// In production mode set AllowInsecureHttp = false
AllowInsecureHttp = true
};
// Enable the application to use bearer tokens to authenticate users
app.UseOAuthBearerTokens(OAuthOptions);
Now I have added some SignalR Hubs to my project and I'd like to authenticate the users in the hubs as well. There are some other questions that deal with how to clients can add bearer token to SignalR connection. Summary:
- One way would be adding it as an ajax default header which is discouraged due to the fact that the JS WebSocket API does not make it possible to set custom headers.. So you fallback to using "Long Polling". (Not what i want)
- Another way is creating a custom
OAuth Provider
and passing the token as query string. This approach is also discouraged because it can become a security issue. But I'd still want to use this method because I don't want Long Polling.
What I Tried:
So I created this class to retrieve my access token from query string.
public class QueryStringOAuthBearerProvider : OAuthBearerAuthenticationProvider
{
public override Task RequestToken(OAuthRequestTokenContext context)
{
var value = context.Request.Query.Get("access_token");
if (!string.IsNullOrEmpty(value))
{
context.Token = value;
}
return Task.FromResult<object>(null);
}
}
And then on the client-side, I do this to pass my access token:
var connection = $.hubConnection();
var hub = connection.createHubProxy('fooHub');
connection.qs = { 'access_token': myAccessToken};
// Init connection
This is the idea but the problem arises when I want to register my QueryStringOAuthBearerProvider
in the Startup.Auth.cs
class.
Here is how I changed my Startup.Auth.cs class:
OAuthOptions = new OAuthAuthorizationServerOptions
{
TokenEndpointPath = new PathString("/Token"),
Provider = new ApplicationOAuthProvider(PublicClientId),
AuthorizeEndpointPath = new PathString("/api/Account/ExternalLogin"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(14),
// In production mode set AllowInsecureHttp = false
AllowInsecureHttp = true
};
// Enable the application to use bearer tokens to authenticate users
app.UseOAuthBearerTokens(OAuthOptions);
// Enable the application to retrieve tokens from query string to authenticate users
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions()
{
Provider = new QueryStringOAuthBearerProvider()
});
So I thought right now I can use:
- The default bearer tokens which are in the header of the requests for my WebAPI calls.
- The custom access tokens which are in the query string for my SignalR Hubs.
Results:
When I'm connecting to my hubs I can retrieve access token from query string and users are authenticated. But when I try to invoke a WebAPI controller action I get System.InvalidOperationException
saying Sequence contains more than one element
. I think it's because I'm registering two ways to authenticate my users through bearer tokens and OWIN doesn't like that.
Question:
How can I have both methods to get the bearer token?
- Default behaviour (Authorization Header) for WebApi calls
QueryStringOAuthBearerProvider
(Query string) for SignalR Hubs.
Please note that I don't want to pass access tokens as query string to my WebApi actions, but only to SignalR Hubs.