5
votes

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:

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:

  1. The default bearer tokens which are in the header of the requests for my WebAPI calls.
  2. 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.

2

2 Answers

8
votes

While blindly searching for an answer, I encountered a post somewhat similar to my question and way down in the comments someone named mahmoud said:

I found the problem. Instead of using app.UseOAuthBearerTokens(OAuthOptions) apply app.UseOAuthAuthorizationServer(OAuthOptions).

It worked in my case and now I can use both methods to get bearer tokens. Here is my modified 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);   // Commented this line.

app.UseOAuthAuthorizationServer(OAuthOptions); // Added this line

// Enable the application to retrieve tokens from query string to authenticate users
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions()
{
    Provider = new QueryStringOAuthBearerProvider()
});
1
votes

I used logic but did some update in QueryStringOAuthBearerProvider class then I got correct result..

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>(context);
    }
}