85

I am trying to write some simple tests User Authentication mechanism which uses Basic Authentication. How can I retrieve the credentials from the header?

string authorizationHeader = this.HttpContext.Request.Headers["Authorization"];

Where do I go from here? There are several tutorials but I new to .NET and authentication, could you explain in your answer exactly step-by-step the what and why you are doing.

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459

4 Answers4

268

From my blog:

This will explain in detail how this all works:

Step 1 - Understanding Basic Authentication

Whenever you use Basic Authentication a header is added to HTTP Request and it will look similar to this:

Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==

Source: http://en.wikipedia.org/wiki/Basic_access_authentication

"QWxhZGRpbjpvcGVuIHNlc2FtZQ==" is just "username:password" encoded in Base64(http://en.wikipedia.org/wiki/Base64). In order to access headers and other HTTP properties in .NET (C#) you need to have access to the current Http Context:

HttpContext httpContext = HttpContext.Current;

This you can find in System.Web namespace.

Step 2 - Getting the Header

Authorization header isn't the only only one in the HttpContext. In order to access the header, we need to get it from the request.

string authHeader = this.httpContext.Request.Headers["Authorization"];

(Alternatively you may use AuthenticationHeaderValue.TryParse as suggested in pasx’s answer below)

If you debug your code you will see that the content of that header looks similar to this:

Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==

Step 3 - Checking the header

You've already extracted the header now there are several things you need to do:

  1. Check that the header isn't null
  2. Check that the Authorization/Authentication mechanism is indeed "Basic"

Like so:

if (authHeader != null && authHeader.StartsWith("Basic")) {
    //Extract credentials
} else {
    //Handle what happens if that isn't the case
    throw new Exception("The authorization header is either empty or isn't Basic.");
}

Now you have check that you are have something to extract data from.

Step 4 - Extracting credentials

Removing "Basic " Substring

You can now attempt to get the values for username and password. Firstly you need to get rid of the "Basic " substring. You can do it like so:

string encodedUsernamePassword = authHeader.Substring("Basic ".Length).Trim();

See the following links for further details:

  1. http://msdn.microsoft.com/en-us/library/system.string.substring(v=vs.110).aspx
  2. http://msdn.microsoft.com/en-us/library/t97s7bs3(v=vs.110).aspx

Decoding Base64

Now we need to decode back from Base64 to string:

//the coding should be iso or you could use ASCII and UTF-8 decoder
Encoding encoding = Encoding.GetEncoding("iso-8859-1");
string usernamePassword = encoding.GetString(Convert.FromBase64String(encodedUsernamePassword));

Now username and password will be in this format:

username:password

Splitting Username:Password

In order to get username and password we can simply get the index of the ":"

int seperatorIndex = usernamePassword.IndexOf(':');

username = usernamePassword.Substring(0, seperatorIndex);
password = usernamePassword.Substring(seperatorIndex + 1);

Now you can use these data for testing.

The Final Code

The final code may look like this:

HttpContext httpContext = HttpContext.Current;

string authHeader = this.httpContext.Request.Headers["Authorization"];

if (authHeader != null && authHeader.StartsWith("Basic")) {
    string encodedUsernamePassword = authHeader.Substring("Basic ".Length).Trim();
    Encoding encoding = Encoding.GetEncoding("iso-8859-1");
    string usernamePassword = encoding.GetString(Convert.FromBase64String(encodedUsernamePassword));

    int seperatorIndex = usernamePassword.IndexOf(':');

    var username = usernamePassword.Substring(0, seperatorIndex);
    var password = usernamePassword.Substring(seperatorIndex + 1);
} else {
    //Handle what happens if that isn't the case
    throw new Exception("The authorization header is either empty or isn't Basic.");
}
starball
  • 20,030
  • 7
  • 43
  • 238
Dawid O
  • 6,091
  • 7
  • 28
  • 36
  • 2
    Perfect answer but note this solution won't work if username or password contains `:` – Roman Marusyk Aug 02 '19 at 10:00
  • 13
    ```IndexOf``` should pick the first occurrence of the character. Therefore, your username cannot contain a colon which is made clear by the RFC 7617: ```Furthermore, a user-id containing a colon character is invalid, as the first colon in a user-pass string separates user-id and password```. – Dawid O Aug 02 '19 at 11:31
25

Just adding to the main answer, the best way to get rid of the "Basic" substring is to use AuthenticationHeaderValue Class:

var header = AuthenticationHeaderValue.Parse(Request.Headers["Authorization"]);
var credentials = header.Parameter;

It will throw a FormatException if the content of the header is not valid, e.g.: the "Basic" part is not present.

Alternatively if you do not want to have exception, use AuthenticationHeaderValue.TryParse

Michael Freidgeim
  • 26,542
  • 16
  • 152
  • 170
pasx
  • 2,718
  • 1
  • 34
  • 26
  • This is quite useful since works with NET 4.5+ and parses both the token parameter and scheme (Basic, Bearer, etc.). +1 – Yogi Oct 23 '21 at 12:47
4

Awesome answer from @DawidO.

If you are just looking to extract the basic auth creds and rely on the .NET magic given you have HttpContext, this will also work:

  public static void StartListener() {
    using (var hl = new HttpListener()) {
      hl.Prefixes.Add("http://+:8008/");
      hl.AuthenticationSchemes = AuthenticationSchemes.Basic;
      hl.Start();
      Console.WriteLine("Listening...");
      while (true) {
        var hlc = hl.GetContext();

        var hlbi = (HttpListenerBasicIdentity)hlc.User.Identity;
        Console.WriteLine(hlbi.Name);
        Console.WriteLine(hlbi.Password);

        //TODO: validater user
        //TODO: take action
      }
    }
  }
sobelito
  • 1,525
  • 17
  • 13
  • 1
    Note with this approach you are stuck to decoding with the iso-8859-1 charset. If your data is actually encoded in utf-8 for example, you will get unexpected results. – Matthew Oct 16 '17 at 19:26
1

Remember, using strings can be less secure. They will remain in memory untill they are picked by GC.

Anup Thakare
  • 89
  • 1
  • 1
  • Could you elaborete why this is a security issue? – bombek Feb 14 '20 at 07:52
  • 1
    @bombek I believe the answer suggests to use SecureString https://learn.microsoft.com/en-us/dotnet/api/system.security.securestring?view=net-5.0. But it’s not recommended any more https://github.com/dotnet/platform-compat/blob/master/docs/DE0001.md – Michael Freidgeim Apr 12 '21 at 12:03