19

Short Version: I need to pass and verify the OWIN bearing token as a query parameter rather than in the request header.

How do I then get the method to authorized based on that token string?

Background: I want to call a webapi method to download a file as a stream (and never want the user to download it from a known file location).

I can't get this to work if I also need to set a custom Request header i.e. the bearer token.

I should be able to pass the token in the query string - but don't know how to get that token to then authenticate the user.

Do I need to filter? Do I need a special claim etc? Does the webapi method need to include "access_token" as one of the function parameters?

BronwenZ
  • 243
  • 1
  • 3
  • 6

3 Answers3

29

For completeness, here's another neat solution.

Extract:

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();
});
Community
  • 1
  • 1
Dunc
  • 18,404
  • 6
  • 86
  • 103
  • 3
    Perfect thanks, I wrapped this up inside my own extension method for IAppBuilder: public static void UseQueryStringAuthentication(this IAppBuilder app) so that I could follow the same practise as that of app.UseQueryStringAuthentication(). It is also worth mentioning that as we are intercepting the call, we have to put this in the pipeline BEFORE the authentication mechanisms are called. So I put this as the first item in my Startup.Auth file. – The Senator May 13 '16 at 17:14
  • 1
    This must appear before call to app.UseOAuthBearerTokens – Carl Sharman Apr 06 '17 at 13:33
  • Yeap, very important that this is before the app.UseOAuthBearerTokens(OAuthOptions) command – Catinodeh Jun 16 '17 at 19:56
  • This works, thanks a ton. But there's a catch. Suppose a user generates a PDF file for download, which has links to resources (i.e. "/downloads/{file_id}"). This works with your approach, but the token gets expired. And after sometime, if I try to open a link from this PDF, it will be unauthorized again. Any ideas how I can work around this? – Nexus Feb 20 '18 at 20:13
  • 1
    @Nexus Yes, URLs containing the bearer token will expire. Either handle this with a 401 "unauthorized" response, redirecting to your login page, which could then redirect back to the /downloads/{...} link with the new token. Or use an unsecured API to serve downloads, but rather than taking `{file_id}`, you could generate a unique token and associate it with your file through your database (or whatever), then create a `/downloads/{file_token}` endpoint. This decoupling will protect your API through obfuscation (i.e. people guessing file IDs) and allow you to disable/expire download links. – Dunc Feb 21 '18 at 09:41
  • @Dunc Ah yes, I see what you mean. I've implemented a prototype and it works fine. Except the only problem is, we're passing access tokens around. In other words, if I copy a attachment link and give it to someone else, they can use it. I think your last suggestion is a better idea. To generate a separate token for downloads, and serve them from another end-point. I'll have to give that a try and get back to you on this. Thanks again. – Nexus Feb 22 '18 at 18:36
  • I tried this but keeping getting "invalid bearer token received" when debugging OWIN. Any ideas? – rukiman Aug 09 '22 at 01:48
9

I wrote about how that works here: http://leastprivilege.com/2013/10/31/retrieving-bearer-tokens-from-alternative-locations-in-katanaowin/

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

or do it like this

    app.UseIdentityServerBearerTokenAuthentication(new IdentityServerBearerTokenAuthenticationOptions
    {
        Authority = IdentityConfig.Authority,
        RequiredScopes = new[] { "api" },
        TokenProvider = new OAuthBearerAuthenticationProvider
        {
            OnRequestToken = ctx =>
            {
                if (String.IsNullOrWhiteSpace(ctx.Token) && ctx.Request.QueryString.HasValue)
                {
                    NameValueCollection parsedQuery = HttpUtility.ParseQueryString(ctx.Request.QueryString.Value);
                    ctx.Token = parsedQuery["access_token"];
                }

                return Task.FromResult(0);
            }
        }
    });
Mr. Pumpkin
  • 6,212
  • 6
  • 44
  • 60