Key Vault Client: Why am I seeing HTTP 401?

Scenario

A common concern with our Key Vault customers is the occurrence of an HTTP 401 (unauthorized) response from the Key Vault.  They are understandably troubled that a malicious attack on the Key Vault could be taking place, and they have alerts in place to notify them of any such responses.  However, it's not necessarily a something that we should be worried about.

What is an HTTP 401?

An overview of HTTP 401 is in order.  Amongst the set of HTTP response status codes, the 400-499 range is set aside for informing the client that there was something wrong or incorrect with the request, to the effect that an authorized valid response could not be returned. There are many response codes available, here are a couple of others:

  • 400 - Bad request.  Something was badly formatted and the server doesn't understand what the client was trying to accomplish

  • 403 - Forbidden. The client sent a properly formatted request, along with credentials that are recognized.  However, even though the client was recognized and may have permission to get some resources, this specific resource is off-limits to the client.  It's an "access denied".

As for the 401, it means "unauthorized".  The client sent a properly formatted request, but the credentials do not allow the request to get the information requested.  For Key Vault, this can be due to at least a couple of reasons:

  • Lack of an access token -  Key Vault uses Azure AAD OAUTH2 authentication.  For a Key Vault to be properly accessed, the AAD OAUTH server must issue an access token to the client, and the client must send this access token with every request to the Key Vault.  If the access token is not present, this will cause the Key Vault to reject the request with 401.

  • Bad token - If there's something wrong with the token, such as:
    • Expired
    • Not yet valid (future use token)
    • Incorrect resource.  OAUTH requires knowing the resource that you want to access in order to issue to the token.  This is a URL, and for all public key vaults, the resource is https://vault.azure.net (see my previous blog post for additional information).  If this resource is not exactly correct, we get 401

You may be wondering the difference between a 401 and 403.  Let's think of a Key Vault as a house that has a security guard blocking the entrance.  Inside the house are a bunch of locked rooms containing secrets, keys and certificates.  If you attempt to go into the house, the security guard tries to figure out who you are.  If the guard cannot, then you are rejected with 401 (unauthorized).  However, you are recognized and can get in, so the guard gives you a set of keys to the rooms, but perhaps not all of the rooms. For every room that you can't get into (but try anyway with the wrong key), you get a 403 (forbidden).  For rooms where you are able to get into successfully, you get a 200 (success).

The Key Vault APIs and 401s

What I am about to describe occurs with the .NET API set, and may also occur with Node.JS, Java, and other APIs.  It does not occur with REST requests.  This will become more clear as I describe the processes.  Let's look at how to use REST first:

REST 

When developers use REST directly, they can control the exact set of commands sent to the Key Vault as well as the parameters. there is a very straight-forward series of events.  You can see actual requests/responses in my previous post:

  1. Request an access token from the AAD OAUTH services using the client id and client secret
  2. Receive a response containing an Access Token
  3. Request an object from the Key Vault.  The request contains the Access Token received in step 2.
  4. Receive a response containing the Key Vault object.

If the developer were to want a second object from the Key Vault, only steps 3 and 4 need to be repeated, unless the token is expired.  When the token expires, steps 1 and 2 must be performed again until a new Access Token is obtained.

APIs

The API sets prevent the need for the developers to manually handle the HTTP requests and parse the responses.  This makes development much faster and easier to think about.  However, the series of events is slightly different.  To show this, I've written a simple Windows form application that is used to make a request for a secret using the Key Vault .NET API set:

 public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        string clientId = "<removed>";
        string clientSecret = "<removed>";
        KeyVaultClient keyVaultClient;
        private void Button1_Click(object sender, EventArgs e)
        {
            Debug.WriteLine("Button1_Click started");
            keyVaultClient = new KeyVaultClient(new KeyVaultClient.AuthenticationCallback(GetToken));
            Debug.WriteLine("Button1_Click ended");
            Debug.WriteLine("======================================");

        }

        public static async Task<string> GetToken(string authority, string resource, string scope)
        {
            Debug.WriteLine("GetToken started");
            var authContext = new AuthenticationContext(authority);
          ClientCredential clientCred = new ClientCredential(clientId, clientSecret);
            AuthenticationResult result = await authContext.AcquireTokenAsync(resource, clientCred);
            Debug.WriteLine("Access Token is: " + result.AccessToken);
            Debug.WriteLine("GetToken ended");
            return result.AccessToken;
        }

        private async void Button2_Click(object sender, EventArgs e)
        {
            Debug.WriteLine("Button2_Click started");
            var Secret = await keyVaultClient.GetSecretAsync("https://kvexplorer.vault.azure.net/secrets/mysecret");
            Debug.WriteLine("Secret Value at " + DateTime.Now.ToString() + ": " + Secret.Value);
            Debug.WriteLine("Button2_Click ended");
            Debug.WriteLine("======================================");
        }
    }

This is what it looks like when I run it:

The two buttons are clearly marked as to what they do.  However, it's when they work is the interesting part.  This is the output window from the code execution when clicking both buttons, first the "Create Key Vault Client", and secondly, "Call Key Vault":

Button1_Click started
Button1_Click ended
======================================
Button2_Click started
GetToken started
Access Token is: eyJ0eXAiOiJKV... <removed>
GetToken ended
Secret Value at 8/15/2019 3:22:39 PM: my password
Button2_Click ended
======================================

Here's the Fiddler trace from the same:

 

  • When "Create Key Vault Client" button is clicked, the "Button1_Click" method is executed. This means that the keyVaultClient object is instantiated, but that's it.  Even though we are passing the GetToken method into the AuthenticationCallback delegate, it does not get executed at this time.

  • However, when the "Call Key Vault" button is clicked for the first time, the following takes place in the Button2_Click method:
    • Client makes an REST call to the Key Vault to retrieve the secret, but without an access token.  This results in HTTP 401.  
    • Client then invokes the GetToken method to make a REST call to the AAD OAUTH servers to get an access token.
    • Client makes a second REST call to the Key Vault to retrieve the secret, but has the token this time - it works!


Now let's examine what happens what we see when we click the "Call Key Vault" button a second time:

Button1_Click started
Button1_Click ended
======================================
Button2_Click started
GetToken started
Access Token is: eyJ0eXAiOiJKV... <removed>
GetToken ended
Secret Value at 8/15/2019 3:22:39 PM: my password
Button2_Click ended
======================================
Button2_Click started
GetToken started
Access Token is: eyJ0eXAiOiJKV... <removed>
GetToken ended
Secret Value at 8/15/2019 3:35:49 PM: my password
Button2_Click ended
======================================

So... that's interesting.  We still executed the GetToken method...  but what does Fiddler show us?

There's only one additional HTTP request made to Key Vault, and it was successful!  This makes sense: since we already have an Access Token, there's no need to reach out to the the AAD servers to get one.  


Deep Dive
At this point I you may be trying to figure out why the Key Vault client does not get the token before making the first attempt to the Key Vault object.  Let's recall that this is a managed process meant to not only to abstract the REST methods away, but perform the operation for three circumstances:

  1. The client has no Access Token (a new Key Vault Client instance)
  2. The client has a valid Access Token
  3. The client has an expired Access Token (there is a one-hour expiration on the Access Token)

This is what's going on under the covers for KeyVaultClients objects:

  1. The Key Vault Client calls a ProcessHttpRequestAsync method to assign the current Access Token to the call to Key Vault.
  2. ProcessHttpRequestAsync calls PreAuthenticate to see if it already has a HttpBearerChallenge object (containing the Authorization URI, Resource URI, and Scope (which is not used in this case)):
    1. If the HttpBearerChallenge is null, null is returned
    2. If the Challenge information exists, then the delegate passed into AuthenticationCallback is called (GetToken) using the HttpBearerChallenge information.  GetToken fetches the Token from AuthenticationContext.AcquireTokenAsync and returns it to the ProcessHttpRequestAsync method. Note that AuthenticationContext.AcquireTokenAsync may have a cached Access Token and may not call out to the AAD Auth servers.
  3. If the Access Token is returned to ProcessHttpRequest, then the ProcessHttpRequestAsync returns the Access Token to the Operation call (GetSecretWithHttpMessagesAsync in our case) and we process the rest of the call to the Key Vault using the Access Token.  This results in a single outbound call, only to Key Vault.
  4. However, if the Access Token is not returned, ProcessHttpRequestAsync makes a call to Key Vault without a token. The response is expectedly HTTP 401.  The reason for this is to understand the HttpBearerChallenge information.  We can see this here:


  5. At this time, a call to PostAuthenticate is made in order to parse the HttpBearerChallenge information from the response, and then also calls into the AuthenticationCallback delegate (GetToken).  The delegate calls AuthenticationContext.AcquireTokenAsync.  Since there's not been an Access Token so far, the AuthenticationContext.AcquireTokenAsync method calls into the AAD Auth servers to fetch the token and return it to the PostAuthenticate method, which then returns it to the ProcessHttpRequestAsync method, and finally the Access Token is added to the HttpRequestMessage passed into the ProcessHttpRequestAsync method.

That was kind of wordy, but there are a lot of Key Vault users who want to understand this stuff. Here's the breakdown:

  1. If we don't have an Access Token, we need to make these calls:
    1. Key Vault without an Access Token to get the Authorization URI and Resource URI
    2. AAD OAUTH servers to get an Access Token
    3. Key Vault with an Access Token to get the secret (or whatever you want to do)
  2. If we have an Access Token, we need to only call the Key Vault. 
  3. If our Access Token is expired:
    1. AAD OAuth Servers to get a new Access Token
    2. Key Vault with the new Access Token to get the secret.

The main issue most seem to have a hard time with is the fact that the initial failed call is to get additional information which is required to make the subsequent call to the OAUTH services for a token.  

 

You can see the code for this using these links:
KeyVaultClient.cs
KeyVaultCredential.cs

 

I hope this post helps you understand why we see the HTTP 401 on occasion.  Please respond to this post and tweet about it if you find it helpful.

Please follow us on Twitter and retweet!
@WinDevMatt @AzIdentity

Add comment