Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Retrieve bearer token from header and/or query string in ASP.NET WebAPI 2

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:

  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.

like image 240
Sobhan Avatar asked Feb 14 '16 09:02

Sobhan


People also ask

How can I get bearer token from Authorization header?

The token is a text string, included in the request header. In the request Authorization tab, select Bearer Token from the Type dropdown list. In the Token field, enter your API key value. For added security, store it in a variable and reference the variable by name.

How do I get an API bearer token?

Tokens can be generated in one of two ways: If Active Directory LDAP or a local administrator account is enabled, then send a 'POST /login HTTP/1.1' API request to retrieve the bearer token. If Azure Active Directory (AAD) is enabled, then the token comes from AAD.

How do I get access token from Web API?

To get this token, you call the Microsoft Authentication Library (MSAL) AcquireTokenSilent method (or the equivalent in Microsoft. Identity. Web). Call the protected API, passing the access token to it as a parameter.


1 Answers

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()
});
like image 86
Sobhan Avatar answered Oct 28 '22 09:10

Sobhan