Search code examples
c#asp.net-mvcoauth-2.0postman

ASP.NET code cannot get OAuth2 token, but PostMan can


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:

PostMan Request

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;
    }
}

Solution

  • 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:

    1. when a client sends a GET request against your application, one thread is taken from the ASP.NET thread pool to serve that request. Let's call this thread THREAD 1. THREAD 1 enters the ASP.NET request context for the incoming HTTP request and starts executing the GetMessage action method.
    2. THREAD 1 starts executing the 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.
    3. Since the 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.
    4. Eventually, the 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.
    5. At this point you have a deadlock: THREAD 1 is waiting for the completion of the 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.