Base64 URL encoding using C#

Base64 is an encoding scheme that allows binary data to be converted to text and back again.

It is useful in a wide variety of scenarios and makes it easier to transfer and store data in many cases.

This is especially true in a web context where everything tends to be based on text content i.e. HTML and CSS files etc.

Having said that, the Base64 standard can be troublesome when it comes to transferring data in HTTP headers and URL query strings. This is where Base64 URL encoding saves the day, helping to prevent issues like double encoding and routing errors.

In this article, I will explain how Base64 URL encoding works in comparison to normal Base64 and show how you can work with Base64 URL encoding using C#.

How it works

In this section, I will provide a simple breakdown and comparison of Base64 against Base64 URL encoding.

If you are interested in a more detailed explanation of how Base64 encoding works, I recommend looking at the Base64 page on Wikipedia, which provides a more in-depth breakdown.

Characters

Base64 is named as such because it uses 64 different characters to encode data, as follows.

  • A-Z (26 characters)
  • a-z (26 characters)
  • 0-9 (10 characters)
  • /
  • +

In the Base64 standard the equals (=) character is used as a ‘padding’ character in addition to the above set of 64 characters to make the string always divisible by 4. Base64 decoding logic will often validate the length of a Base64 string before decoding it.

Base64 URL encoding uses the same characters as Base64 except for the last two from the list above which are replaced by the underscore (_) and hyphen (-) characters respectively. Additionally, the equals (=) padding characters are often omitted.

Comparison

For the sake of simplicity, let’s look at an example of converting some plain text to Base64 (as opposed to thinking of things in terms of binary data).

Given the text “What does 2 + 2.1 equal?? ~ 4” the output of the Base64 encoding is as follows.

V2hhdCBkb2VzIDIgKyAyLjEgZXF1YWw/PyB+IDQ=

Note that I’ve used the question mark (?) characters and the tilde (~) character in the sample string to force the slash (/) and plus (+) characters to form part of the output.

The same data encoded as a Base64 URL encoded string is as follows.

V2hhdCBkb2VzIDIgKyAyLjEgZXF1YWw_PyB-IDQ

As you can see, the slashes (/) are replaced with underscores (_) and the plus (+) symbols are replaced with hyphens (-).

The equals (=) characters at the end are removed. This helps to prevent these from being confused with field separators.

Aside from the above differences, Base64 URL encoding is identical to the Base64 standard.

Check out base64url.com which provides an interface that allows you to convert to and from Base 64 URL encoding and Base64 encoding and shows the output values of both encoding methods at the same time.

Problems

So what problems can we face when using Base64 encoding in a web context?

Consider that you want to transfer some data in the query string of a URL. In the context of a web URL, the slash (/) and plus (+) symbols have special meanings. To solve this problem reliably, Base64 URL encoding uses the underscore (_) and hyphen (-) characters which do not require escaping.

One of the benefits of using Base64 URL encoding is that it prevents you from having to worry about how a piece of data will be transferred across the web.

For example, imagine you are storing small XML documents in your database that are encoded using Base64 URL encoding and you need to transfer these to a server via a REST API call. Thanks to the Base64 URL encoding, you have the flexibility to send the data in the URL query string, as a header, or in the body without any concerns about reserved characters being used.

Note that it’s still important to consider other factors such as the maximum URL length which will vary across different web servers.

Usage in code

In the following sub-sections, I’ll cover the options there are for working with Base64 URL encoding using C# and .NET.

I’ll then follow this up with the implementation of a standalone class that handles encoding and decoding.

Framework/Packages

.NET has built-in static methods such as Convert.ToBase64String and Convert.FromBase64String that can be used to conveniently convert data to and from Base64 encoded strings.

When it comes to Base64 URL encoding, unfortunately, there isn’t a built-in implementation that is part of the core .NET libraries, however, you can instead avail of the Microsoft.IdentityModel.Tokens NuGet package.

The Microsoft.IdentityModel.Tokens NuGet package provides a Base64UrlEncoder class that exposes static methods to allow you to convert data to and from Base64 URL encoded strings.

Standalone class

If you don’t want to take a dependency on the Microsoft.IdentityModel.Tokens NuGet package (or other similar packages) for any reason, you will need to consider writing some code to handle the encoding logic.

Instead of using a NuGet package, you can include your own Base64UrlEncoder implementation in your project as a lighter-weight alternative that provides the same functionality. I have included the C# source code for this below.

using System.Text;
 
namespace Base64UrlEncoding
{
    /// <summary>
    /// Encodes and Decodes strings using Base64 URL encoding.
    /// </summary>
    public static class Base64UrlEncoder
    {
        #region Constants
 
        private const char   Base64Character62        = '+';
        private const char   Base64Character63        = '/';
        private const string Base64DoublePadCharacter = "==";
        private const char   Base64PadCharacter       = '=';
        private const char   Base64UrlCharacter62     = '-';
        private const char   Base64UrlCharacter63     = '_';
 
        #endregion
 
        #region Methods
 
        /// <summary>
        /// Converts the specified Base64 URL encoded string to a UTF8 string.
        /// </summary>
        /// <param name="s">The Base64 URL encoded string to convert</param>
        /// <returns>A UTF8 string</returns>
        public static string Decode(string s)
        {
            return Encoding.UTF8.GetString(DecodeBytes(s));
        }
 
        /// <summary>
        /// Converts the specified Base64 URL encoded string to a byte array.</summary>
        /// <param name="s">The Base64 URL encoded string to convert</param>
        /// <returns>A byte array</returns>
        public static byte[] DecodeBytes(string s)
        {
            if (s == nullthrow new ArgumentNullException(nameof(s));
 
            // Replace - with +
            s = s.Replace(Base64UrlCharacter62, Base64Character62);
 
            // Replace _ with /
            s = s.Replace(Base64UrlCharacter63, Base64Character63);
 
            // Check padding.
            switch (s.Length % 4)
            {
                case 0// No pad characters.
                    break;
                case 2// Two pad characters.
                    s += Base64DoublePadCharacter;
                    break;
                case 3// One pad character.
                    s += Base64PadCharacter;
                    break;
                default:
                    throw new FormatException("Invalid Base64 URL encoding.");
            }
 
            return Convert.FromBase64String(s);
        }
 
        /// <summary>
        /// Converts the specified UTF8 string into a Base64 URL encoded string.
        /// </summary>
        /// <param name="s">The UTF8 string to convert</param>
        /// <returns>A Base64 URL encoded string</returns>
        public static string Encode(string s)
        {
            if (s == nullthrow new ArgumentNullException(nameof(s));
 
            return Encode(Encoding.UTF8.GetBytes(s));
        }
 
        /// <summary>
        /// Converts the specified byte array to a Base64 URL encoded string.
        /// </summary>
        /// <param name="bytes">The byte array to convert</param>
        /// <returns>A Base64 URL encoded string</returns>
        public static string Encode(byte[] bytes)
        {
            if (bytes == nullthrow new ArgumentNullException(nameof(bytes));
 
            string s = Convert.ToBase64String(bytes, 0, bytes.Length);
            s        = s.Split(Base64PadCharacter)[0];                     // Remove trailing padding i.e. = or ==
            s        = s.Replace(Base64Character62, Base64UrlCharacter62); // Replace + with -
            s        = s.Replace(Base64Character63, Base64UrlCharacter63); // Replace / with _
 
            return s;
        }
 
        /// <summary>
        /// Converts the specified byte array to a Base64 URL encoded string.
        /// </summary>
        /// <param name="bytes">The byte array to convert</param>
        /// <param name="offset">The byte array offset</param>
        /// <param name="length">The number of elements in the byte array to convert</param>
        /// <returns>A Base64 URL encoded string</returns>
        public static string Encode(byte[] bytesint offsetint length)
        {
            if (bytes == nullthrow new ArgumentNullException(nameof(bytes));
 
            string s = Convert.ToBase64String(bytes, offset, length);
            s        = s.Split(Base64PadCharacter)[0];                     // Remove trailing padding i.e. = or ==
            s        = s.Replace(Base64Character62, Base64UrlCharacter62); // Replace + with -
            s        = s.Replace(Base64Character63, Base64UrlCharacter63); // Replace / with _
 
            return s;
        }
 
        #endregion
    }
}

Note that you should change the Base64UrlEncoding namespace according to your project.

The above code provides the same API that is exposed by the Base64UrlEncoder class included in the Microsoft.IdentityModel.Tokens NuGet package.

The underlying implementation is almost identical and it encodes and decodes data in exactly the same manner, so it’s fully compatible with the Microsoft.IdentityModel.Tokens NuGet package. In other words, if you encoded a string on the server within an ASP.NET Core web application using the Microsoft.IdentityModel.Tokens NuGet package, you can safely decode it within a native client-side application using the above code and vice-versa.

The code itself is quite straightforward, so there’s no need to provide a full walkthrough. However, it’s worth pointing out the usage of the Convert.ToBase64String and Convert.FromBase64String methods that are built into .NET.

Other than that, the code is mostly focused on string splits and string replacements, as well as handling the padding when decoding to ensure that the Base64 string is properly formatted before decoding is attempted.

Using the class methods

The Base64UrlEncoder class shown in the previous sub-section allows you to encode and decode string and byte arrays using the static methods that it exposes.

Below is an example of how to encode and decode a sample string of data.

using Base64UrlEncoding;
 
string encodedString = Base64UrlEncoder.Encode("Base64 URL encoding using C#");
 
Console.WriteLine(encodedString); // Output: QmFzZTY0IFVSTCBlbmNvZGluZyB1c2luZyBDIw
 
string decodedString = Base64UrlEncoder.Decode(encodedString);
 
Console.WriteLine(decodedString); // Output: Base64 URL encoding using C#

Note that you should change the using statement above to match the namespace you chose to enclose your version of the Base64UrlEncoder class within.

Alternatively, you can pass in some binary data (e.g. an image converted to a byte array) and one of the overloaded methods will be called instead.

Summary

In this article, I have explained what Base64 encoding is and how it differs from Base64 URL encoding.

I provided examples of the output of Base64 encoding versus Base64 URL encoding and considered some of the problems that can be encountered when using Base64 strings in URL query strings specifically.

I then moved on to look at the options we have in .NET for converting to and from Base64 and Base64 URL encoded strings using both framework methods and NuGet packages.

Lastly, I provided the source code for the Base64UrlEncoder class along with a further C# code example that demonstrates how to encode and decode Base64 URL encoded strings.


I hope you enjoyed this post! Comments are always welcome and I respond to all questions.

If you like my content and it helped you out, please check out the button below 🙂

Comments

This site uses Akismet to reduce spam. Learn how your comment data is processed.