2

I'm calling a REST api like so:

HttpClient client;
var uri = new Uri(Const.GetUserAccount);
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("accessToken", App.AccessToken);
var response = await client.GetAsync(uri);

And here is my action signature (accessToken is null):

public async Task<ActionResult> GetAccountAsync([FromHeader] string accessToken)

The token is in Request.Headers.HeaderAuthorization and its value is:

"accessToken" + a space + the guid

That seems odd. Shouldn't there be a name value pair? Like:

"accessToken": "theGUID"

Is this why it's not binding? If so, how do I pass it correctly? If not, what am I doing wrong?

4
  • 2
    I think the parameter should be called authorization rather than accessToken Commented Sep 12, 2018 at 22:37
  • How will the [FromHeader] attribute know where to get accessToken from? Commented Sep 12, 2018 at 23:35
  • what is your token?? JWT? Commented Sep 12, 2018 at 23:39
  • 1
    No, it shouldn't be "accessToken": "theGUID" - The whole header is actually Authorization: accessToken theGUID. The : demarcates the header/value pair. As @CamiloTerevinto states, you can change the parameter to authorization, which will give you a value of accessToken theGUID. Commented Sep 13, 2018 at 7:45

1 Answer 1

8

Is this why it's not binding?

The reason is that your action method expects a accessToken from request headers :

public async Task<ActionResult> GetAccountAsync([FromHeader] string accessToken)

While you there's no such a AccessToken: xxx_yyy_zzz header in the request .

If you send a request as below :

GET https://localhost:44323/api/values/account HTTP/1.1
accessToken : xxx_yyy_zzz

The ModelBinder will bind the accessToken.

If so, how do I pass it correctly?

I'm not sure why you want to get the accessToken within an action method . However , if you do need the access token by model binding , there're at least two ways to do that :

One way to do that is to change your action method to get the Authorization header directly:

public async Task<ActionResult> GetAccount2Async([FromHeader] string authorization) 
{
    if (String.IsNullOrEmpty(authorization)) { /* */ }
    if (!authorization.StartsWith("accessToken",StringComparison.OrdinalIgnoreCase)) { /* */ }

    var token = authorization.Substring("accessToken".Length).Trim();
    // ...
}

It will works when you send a request with a header of Authorization : accessToken xxx_yyy_zzz .

However , the approach above is not nice and clean . A better way is to create a custom ModelBinder .

Firstly Let's create a dummy class to hold the accessToken value :

public class AccessTokenAuthorizationHeader
{
    public string TokenValue { get; set; }
}

And here's a simple model binder that will retrieve access token from headers :

public class AuthorizationHeaderBinder : IModelBinder
{
    const string DEFAULT_ACCESS_TOKEN_AUTH_HEADER_PREFIX = "accessToken";
    public Task BindModelAsync(ModelBindingContext bindingContext)
    {
        if (bindingContext == null) { throw new ArgumentNullException(nameof(bindingContext)); }

        var modelName = bindingContext.BinderModelName;
        if (string.IsNullOrEmpty(modelName)) { modelName = DEFAULT_ACCESS_TOKEN_AUTH_HEADER_PREFIX; }

        var authorization = bindingContext.HttpContext.Request.Headers["Authorization"].FirstOrDefault();
        if (String.IsNullOrWhiteSpace(authorization)) {
            return Task.CompletedTask;
        }
        if (!authorization.StartsWith(modelName, StringComparison.OrdinalIgnoreCase)) {
            return Task.CompletedTask;
        }
        var token = authorization.Substring(modelName.Length).Trim();

        bindingContext.Result = ModelBindingResult.Success(new AccessTokenAuthorizationHeader() {
            TokenValue =token,
        });
        return Task.CompletedTask;
    }
}

Lastly , decorate the previous AccessTokenAuthorizationHeader with a ModelBinderAttribute:

[ModelBinder(BinderType =typeof(AuthorizationHeaderBinder))]
public class AccessTokenAuthorizationHeader
{
    public string TokenValue { get; set; }
}

And now we can bind it automatically :

[HttpGet("Account3")]
public async Task<ActionResult> GetAccount3Async(AccessTokenAuthorizationHeader accessToken) {
    var result =new JsonResult(accessToken?.TokenValue);
    return result;
}

Let's test it with a requset :

GET https://localhost:44323/api/values/account3 HTTP/1.1
Authorization : accessToken 111111

The response will be :

HTTP/1.1 200 OK
Transfer-Encoding: chunked
Content-Type: application/json; charset=utf-8
Server: Kestrel
X-SourceFiles: =?UTF-8?B?RDpccmVwb3J0XDIwMThcOVw5LTEzXFNPLkF1dGhvcml6YXRpb25IZWFkZXJcQXBwXEFwcFxhcGlcdmFsdWVzXGFjY291bnQz?=
X-Powered-By: ASP.NET
Date: Thu, 13 Sep 2018 01:54:25 GMT

"111111"
Sign up to request clarification or add additional context in comments.

5 Comments

You can also access the headers via HttpContext.Request.Headers from any action method. No need for custom binding.
@Brad Well , that's exactly what I did in the first approach .However , if in that case , you have to validate whether the header of Authorization is null or empty , whether it's Authorization : accessToken xxx_yyy_zzz or Authorization : access-Token xxx_yyy_zzz or something else . And if there're mutilple action methods that need the accessToken , I'm sure our heads will hurt .
You might want to use ModelBinding so unit tests are easier. You can pass in a string directly on the method call vs. having to setup a context, a request, a headers collection and the actual header name/value. Sure you can do all of that in every unit test and every controller method but why not write it once and reuse it everywhere?
@NoRefundsNoReturns Thanks for your suggestion. But could you please elaborate a little more ?I do create a ModelBinder in my second approach.
My comment was mostly toward @Brad whose approach would require you to setup a lot of extra cruft for every unit test.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.