I have some code that I am trying to (in my opinion) simplify to get the OAuth token for a Web API request.
I tested the process in PostMan originally setting the auth token request as part of the "authorization" tab of the main request. This works great. If I set the auth token request as its own separate entity in PostMan also works great.
In C# however, it never responds. I have other code where this same request works just fine (different service, etc, but same code layout).
C# code:
public static async Task<string> GetAccessTokenAsync()
{
try
{
using (var client = new HttpClient())
{
client.BaseAddress = new Uri(AuthTokenUrl);
var request = new FormUrlEncodedContent(new Dictionary<string, string>
{
["grant_type"] = "client_credentials",
["client_id"] = AuthClientId,
["client_secret"] = AuthClientSecret,
["scope"] = AuthScope
});
var resp = await client.PostAsync("", request);
if (resp.IsSuccessStatusCode)
{
var content = await resp.Content.ReadAsAsync<AccessTokenModel>();
return content.access_token;
}
Log.Error("API Token Request unsuccessful. Status code: {code}; Reason: {reason}, {@request}", resp.StatusCode, resp.ReasonPhrase, request);
return null;
}
}
catch (Exception ex)
{
Log.Error(ex, "Error while retrieving auth token for API.");
return null;
}
}
Postman:
Working C# example:
public static async System.Threading.Tasks.Task<string> GetOktaToken()
{
using (var oktaClient = new HttpClient())
{
oktaClient.BaseAddress = new Uri(AuthUrl);
var content = new FormUrlEncodedContent(new[] {
new KeyValuePair<string,string>("grant_type", "client_credentials")
, new KeyValuePair<string, string>("scope", "scope.read")
, new KeyValuePair<string, string>("client_id", ClientId)
, new KeyValuePair<string, string>("client_secret", ClientSecret)
});
var resp = await oktaClient.PostAsync(string.Empty, content);
var oktaresp = await resp.Content.ReadAsAsync<OktaResponse>();
return oktaresp.access_token;
}
}
The problem you had is an interesting one. It has nothing to do with the OAuth2 token negotiation, instead it was a deadlock in your action method: this is why you noticed that your HTTP request never completed and got stuck with no apparent explanation.
The root cause of this problem is that in classic ASP.NET (that is .NET framework ASP.NET implementation) there is a thing called ASP.NET request context. The main rule for the ASP.NET request context is that it only allows one thread in at the time. This basically means that if there is a thread executing inside a given ASP.NET request context, no other thread can be executed in that same request context.
Remember also how an asynchronous method works when it encounters the await
keyword: it captures the context in which it is executed and, by default, it tries to resume its execution inside the captured context.
The exact meaning of the word context entirely depends on the type of application: if you have a classic ASP.NET application this context is the ASP.NET request context mentioned above.
Consider now this example. You have a classic ASP.NET application, where you have the following action method. Notice that this action method is synchronous and that it blocks its execution when the asynchronous method GetMessageAsync
is invoked:
public sealed class ValuesController : ApiController
{
[HttpGet]
public IHttpActionResult GetMessage()
{
var message = GetMessageAsync().Result;
return new TextResult(message, Request);
}
}
Here is the implementation of the GetMessageAsync
asynchronous method:
public async Task<string> GetMessageAsync()
{
await Task.Delay(5000);
return "Hello World!";
}
If you create a simple web api application which tries to execute this code, you will get a deadlock in your application. Here is why:
GetMessage
action method.GetMessageAsync
method. Execution continues until the Task.Delay
method is invoked. This method returns an uncompleted Task
which will eventually complete after 5 seconds: this Task
indicates that the execution of Task.Delay
is not completed and will eventually complete in the future. Since GetMessageAsync
is an asynchronous method and the await
keyword is used in front of Task.Delay
, the execution of GetMessageAsync
is interrupted and the method returns an uncompleted Task
. This Task
indicates that the execution of GetMessageAsync
is not completed and will eventually complete in the future. Notice also that the await
keyword has automatically captured the execution context (which is, in this case, the ASP.NET request context) so that, once the Task
returned by Task.Delay
will be completed, the execution of GetMessageAsync
will resume on the captured context.Task
returned by GetMessageAsync
is uncompleted, the execution of the GetMessage
method cannot continue beyond the first line. The method stops at the line where GetMessageAsync
method is invoked and THREAD 1 stays there blocked, synchronously waiting for the completion of the Task
returned by GetMessageAsync
. This happens because the get accessor of Task<T>.Result
property is blocking.Task
returned by Task.Delay
completes. At this point, the runtime needs to resume the execution of the GetMessageAsync
method inside the context captured by the await
keyword. Remember that this context is the ASP.NET request context for the incoming HTTP request. A thread is then taken from the ASP.NET thread pool, let's call this thread THREAD 2. This thread should, at this point, enter the ASP.NET request context and continue the execution of the GetMessageAsync
method past the await
keyword. Unfortunately this is not allowed, because there is already a thread executing inside the ASP.NET request context: it is the THREAD 1 thread, which is stuck waiting for the completion of the Task
returned by the GetMessageAsync
method.Task
returned by the GetMessageAsync
method and THREAD 2 is waiting for the ASP.NET request context to be available in order to execute the remaining part of the GetMessageAsync
method. ASP.NET request context is not available, since THREAD 1 is currently executing inside that context.The solution to avoid this kind of situations is to never ever blocks on asynchronous code. If you are writing modern C# code in a platform where the async
and await
keywords are available, always use asynchronous methods and use the await
keyword when you invoke asynchronous code. APIs like Task.Wait()
and Task<T>.Result
are blocking and should never be used if you are inside an asynchronous context.
Refer to this article if you want further information on this topic.