4

I have a WebAPI 2.2 w/ OData V4 service that uses a custom authentication mechanism. Basically it's similar to OAuth's Bearer authentication such that a request is made to a specific endpoint with the username and password and a token is returned. The token is then included in the Authorization header of all subsequent requests. I'm using the OData V4 Client Code Generator (2.3.0) and adding the Authorization header manually using the DataServiceContext's BuildingRequest event like thus...

private void GetData() {
    var context = new DataAccess.Default.Container(new Uri("http://myserver/API/"));

    context.BuildingRequest += onBuildingRequest;
    var data = context.Computers.ToList();
}
void onBuildingRequest(object sender, Microsoft.OData.Client.BuildingRequestEventArgs e)
{
    if (_token == null)
        _token = GetToken();
    e.Headers.Add("Authorization", "MyToken " + _token);
}

The problem I'm having is the token expires after a certain amount of time, so after a while I'll start getting a 401 response, which causes an exception to be thrown at the point that the IQueryables from the context gets their GetEnumerator called (the ToList call in the above code). I could wrap every single place that I enumerate an endpoint but that is less than ideal. I've found that I can detect a 401 in the DataServiceContext's ReceivingResponse event and mark that the token has expired, however that doesn't prevent the call from failing it will just make subsequent calls work.

void context_ReceivingResponse(object sender, ReceivingResponseEventArgs e)
{
    if (e.ResponseMessage.StatusCode == 401)
    {
        _token = null;
    }
}

So at this point I'm trying to figure out a way to handle a 401 without requiring every call (which will often be when enumerating an IQueryable) to be wrapped in a try/catch. I tried to figure out a way to have the web requests handle my custom authentication in a manner similar to how it handles Basic authentication (if the server responds with a 401 and a WWW-Authenticate header, and a credential has been specified for Basic authentication another request will be sent automatically with the needed Authentication header) but have had no luck.

Any help/suggestions would be appreciated.

MHollis
  • 1,449
  • 9
  • 23

2 Answers2

1

The solution lies within AuthenticaionManager class. By registering you own implementation of IAuthenticationModule through its Register method you'll be able to do what you're looking for.

You'll also probably need another class which implements ICredentials and handles your authentication parameters like your token endpoint url etc. and then pass it to your DataServiceContext. You can even use it to cache your last token and its expiration time.

The only way that I've found so far to handle an expired token, is to store the expiration time while obtaining the token from authorization server and refresh it if it's close to be expired. And by "close" I mean if it'll be expired in n seconds. I preferred this threshold to be my network timeout for retrieving a token. If it has been expired or even close to be expired I'll get a new token from the server without even trying the last one. Feel free to use whatever measurements you please in this section. ;) But there are no ways to respond to your authorization server's challenge more than once and it won't challenge you forever.

Have a look at here for an example about the mentioned class and interfaces.

And BTW for using it in a Portable Class Library you might be a little bit out of luck. There are no signs of AuthenticaionManager and IAuthenticationModule in pcl. Using some kind of abstraction might help.

Hosein
  • 91
  • 1
  • 3
0

Perhaps you forgot to hook context_ReceivingResponse in your GetData? (i don't see it) Otherwise log the http-headers in the send and receiving-events to be sure.

neumn
  • 1
  • 2