7

I have a problem with my Web Api Project. I have files stored in my Database and want to call them directly in a new window to view/save (URL like : /api/Files/5 - 5 beeing the FileId)

I got everthing working with the Bearer Token for my general AJAX requests with AngularJS for normal Data and it works like a charm. For the file I created a Controller that shows the file in the browser with the corresponding MIME-Type. But now that I changed the action to [Authorize] I get an Access Denied which is correct because I didnt pass an access_token in the HTTP-Header.

I did quite some research if it is possible to pass the Token via the querystring but didn't find anything helpful.

Now my plan is to remove the [Authorize] Attribute from my Controller and try to validate the token myself but I don't know how.

Anyone know how I can get it to work?

Marvin
  • 101
  • 1
  • 4

4 Answers4

32

I implemented bearer token authentication in my app (AngularJS, WebAPI 2) and I had similar problem - I needed to allow downloading files by clicking on a link. When you click on a link headers are not sent. :( So, I sent the token value in a query string to download a file

.../mywebapp/api/files/getfile/3?access_token=jaCOTrGsaak6Sk0CpPc1...

and set "Authorization" header to the token value in Startup.Auth.cs. Here is the code:

public void ConfigureAuth(IAppBuilder app)
{
    //It needs for file downloads
    app.Use(async (context, next) =>
    {
        if (context.Request.QueryString.HasValue)
        {
            if (string.IsNullOrWhiteSpace(context.Request.Headers.Get("Authorization")))
            {
                var queryString = HttpUtility.ParseQueryString(context.Request.QueryString.Value);
                string token = queryString.Get("access_token");

                if (!string.IsNullOrWhiteSpace(token))
                {
                    context.Request.Headers.Add("Authorization", new[] { string.Format("Bearer {0}", token) });
                }
            }
        }

        await next.Invoke();
    });
    // Enable the application to use bearer tokens to authenticate users
    app.UseOAuthBearerTokens(OAuthOptions);
}
Forward
  • 321
  • 3
  • 7
  • 1
    This worked for me and was the easiest of several options I tried. That said, there are other approaches that might be best, so don't use this until you've checked out alternative methods for yourself. – john west Oct 22 '14 at 21:29
  • 1
    I wish I could vote this up several times. Smart, concise, elegant...etc :) – Korayem Dec 02 '15 at 19:45
  • @johnwest please share with us the alternative methods – Korayem Dec 02 '15 at 19:46
  • Thanks a lot. this is perfect solution for the file download. i have tried to download file using POST method with applying bear token in header, but unable to download file using post method. this is secure and perfect solution for me. – Shehan Silva Feb 25 '22 at 08:08
4

This feature is already built in - I wrote about it here:

http://leastprivilege.com/2013/10/31/retrieving-bearer-tokens-from-alternative-locations-in-katanaowin/

leastprivilege
  • 18,196
  • 1
  • 34
  • 50
2

For ASP .Net Core I did something like this based on Forward's answer

Extension Method

  public static void UseQueryStringBearerValidation(this IApplicationBuilder app)
    {
        //It needs for file downloads
        app.Use(async (context, next) =>
        {
            if (context.Request.QueryString.HasValue)
            {
                if (string.IsNullOrWhiteSpace(context.Request.Headers["Authorization"].ToString()))
                {
                    var queryString = QueryHelpers.ParseQuery(context.Request.QueryString.Value);
                    var token = queryString["access_token"].ToString();

                    if (!string.IsNullOrWhiteSpace(token))
                    {
                        context.Request.Headers.Add("Authorization", new[] {$"Bearer {token}"});
                    }
                }
            }

            await next();
        });
    }

Usage

StartUp.cs -> Configure() method

            app.UseCustomExceptionHandler();
            app.UseQueryStringBearerValidation(); // <-- add before Jwt Handler
            app.UseCustomJwtBearerValidation();
            app.AddHttpContextProperties();
            app.UseStaticFiles();
            app.UseMvc(MiddlewareAppConfiguration.AddRouteMappings);
dynamiclynk
  • 2,275
  • 27
  • 31
1

Although I'm not sure it's a very good idea, you could implementing a DelegatingHandler to achieve what you are looking for.

public class QueryStringBearerToken : DelegatingHandler
{
    protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        var bearerToken = request.GetQueryNameValuePairs()
                                 .Where(kvp => kvp.Key == "bearerToken")
                                 .Select(kvp => kvp.Value)
                                 .FirstOrDefault();

        if(!String.IsNullOrEmpty(bearerToken))
        {
            request.Headers.Add("Authorization", "Bearer " + bearerToken);
        }
        return base.SendAsync(request, cancellationToken);
    }
}

This handler will look for the query string named "bearerToken" and, if it exists, will add it to the request header for the subsequent handlers / filter to process. You might want to check first if the header is already present and not override in this case. You can add this handler in your configuration phase in the usual fashion:

config.MessageHandlers.Insert(0, new QueryStringBearerToken ());

A request for /YourRoute?bearerToken=theToken will pass in the DelegatingHandler, adding the token passed in the query string to the list of headers in the original request and the regular Bearer Token authentication will look for the header and find it.

Simon Belanger
  • 14,752
  • 3
  • 41
  • 35
  • Thx for the reply. It kinda works but it doesn't. The debugger hits the code and the Token got read correctly but I still get an error message that Im not authorized. If I try to hardcode a token I get a duplicate error for requests that actually have an authorzation attribute... My guess is that this handler kicks in after validation? – Marvin Feb 21 '14 at 17:49
  • @Marvin The duplicate error is expected. As I said, I didn't put the logic to handle the case where there is already an Authorization header in the request. Placing the delegating handler first in the pipeline (`Insert(0, ...)`) should have it executed before the authorization kicks in. – Simon Belanger Feb 21 '14 at 18:00
  • The other point to be careful is the encoding of the token if passed in a query string. Since `=` can be part of the token, it might truncate the value from `GetQueryNameValuePairs()`. You should check whether the token you pass in the query string is the same as the `bearerToken` variable when you place a breakpoint. If this is the case, it would explains the 401 since the bearer token is not recognized. – Simon Belanger Feb 21 '14 at 18:02
  • Just checked it. I copied the token from the variable in the debugger and pasted it in fiddler with the Authorization and there I got a 200. So the token got read correctly. – Marvin Feb 21 '14 at 18:10