Validating Integrity: Google Authentication Using OAuth2 and ASP.NET C#

For those just tuning in, here’s a quick recap of what we’ve done so far:

Part One explains why I’m not using DotNetOpenAuth and why you should reconsider blindly using it for your own web-based applications. We also went over the Google API console and examined the initial code used to send the user off to Google’s user authentication service. Part Two covered the actual process of getting permission from Google to view the user’s data (their email address) and using that permission to make a call back to Google to actually get the necessary information.

Here’s the thing. Google makes it clear in their “Using OAuth2” guide that there are serious security implications with accessing a user’s Google account information, and if you don’t know what you’re doing and don’t take the appropriate security measures, you could accidentally make it really easy for anyone – including someone from 4chan – to pose as one of your users.

This isn’t a lie. When I was originally trying to figure out how to handle this whole OAuth2 beast for my project, every article I found about it either used DotNetOpenAuth or did nothing to verify the integrity of the user’s data. The first step we took to mitigate this was to use a unique session ID that was passed to Google, passed back to our application, and checked against the ID in the user’s web session.

There’s something even more special we can do, though, and it uses fancy cryptography and certificates and stuff! Wow! If you want to go on this adventure with me, just click the jump. Otherwise, it might be easier if you leave now and judge the cuteness of others’ kittens instead.

(If you went to Kittenwar anyhow, I understand. I won’t judge you.)

The official IETF documentation on the JWT spec provides some insight into what the third segment of a token is actually for. This particular section is what we care about – verifying the JWT against a SHA-256 digital signature.

The steps are simple. The implementation? Not so much – but that doesn’t mean we can’t do it!

If you remember from part two, Google sends the user’s information to our application in the form of a JWT, or JSON web token. One line is all you need to convert that line of plain text into an array that separates out each of the three segments of the JWT, where gli.id_token is the actual JWT string:

We’ve already dealt with the payload in part two. What we care about are the header and the signature, which are stored in tokenArray[0] and tokenArray[2].

Parsing the JWT Header

The header is a plaintext, Base64-encoded JSON array. I can’t seem to find the page in Google’s documentation that explains what the elements of this array are, but it’s pretty easy, since there’s only two: alg indicates what hashing algorithm was used for the signature in the third segment of the JWT, and kid gives us the ID of the Google public certificate that is paired with the private key Google used to sign the hash.

Just like how we handled the payload, we’re going to make a class containing these elements…

…and then use the JavaScriptSerializer class to parse it.

What we need is the second element, called kid. Google has already documented that they use SHA-256 to hash the data we’re verifying, so we don’t really need the first element.

Google provides an HTTP-accessible copy of the public certificates that are paired with the private keys used for signing. For security purposes, these certificates change about every 24 hours. For bandwidth purposes (and in order for our code to actually work), we want to cache a copy of each certificate on our own server and use that for the actual validation process.

Caching Google’s Public Certificates

You can take a look at Google’s public certificates here. As you can see, they too are stored in a JSON object. Unlike the header and payload that we’ve already dealt with, however, this array uses dynamic values for each element of the array, so we can’t just make a class to reference when parsing this array. Instead, we need to use that kid value, which is the key in this JSON array. The value is the certificate itself.

I broke this out into a separate function that is given the kid for the purposes of caching the certificate.

First thing, we need to see if the certificate is already cached. To keep things really easy, I’m caching them in C:certs and using the certificate ID itself as the file name.

Even though the certificate is really just plain text, it needs to use the .cer file extension, as you can see above. If the certificate already exists, we don’t need to anything else, and the function is complete.

Of course, you probably want to see the rest of the code, so we’ll assume the certificate hasn’t been cached locally yet.

First, we’ll pull the plaintext from Google’s certificate URL using WebRequest and store it in a string variable called certs.

Next, we’ll use JavaScriptSerializer to serialize the text and pull out the certificate that matches the kid provided by Google when the user logged in. This is accomplished by using a Dictionary object, which can use dynamic key names. You can check out the MSDN documentation on this class here. We’ll store the dictionary in a variable named cts, for “certificates”. I know, I use super creative variable names…

Now that we have our dictionary, we can pull the certificate text we actually need, and write that text to a new file on the server.

One important thing to note here – Google uses two certificates each day, and rotates them out daily. At some point, you’re going to want to clear out old cached certificates from your server. I’d recommend writing a script that runs every morning and deletes certificates that are more than 48 hours old, but that’s another show.

For anyone concerned about bandwidth – this function only goes out to the Internet to download a certificate once. It should run, at the absolute most, twice every 24 hours.

Now that we have our certificate, we can take a look at the third segment of the JWT – the signature – and use the certificate and the signature to validate our data.

Validating the JWT Digital Signature

First, we’re going to create a bool that returns true if the signature is valid or false if the validation fails. This function is going to take both the key ID and the original JWT provided by Google, and will use the verifySignature function we just created.

Following the JWT spec, the toVerify string contains the first two segments of the JWT, concatenated with a period. The signature string is just the third segment as it is sent in the JWT.

Here’s what a JWT signature looks like:

This documentation explains what this gibberish actually is. I hate clicking external links as much as you do, so here’s a summary:

  • The first two Base64-encoded segments of the JWT are concatenated with a period.
  • That string is then hashed (Google uses SHA-256).
  • The hash is digitally signed with the provider’s (Google in this case) private key.
  • The hash is Base64-encoded an appended to the end of the first two segments, again concatenated with a period.

Seems straightforward enough, right? In order to actually use this signature to ensure that the first two segments of the JWT are valid and not corrupted, we just have to follow three steps. Three steps doesn’t sound very scary.

  1. Take the Encoded JWS Signature and Base64url decode it into a byte array. If decoding fails, the signed content MUST be rejected.
  2. Submit the UTF-8 representation of the JWS Signing Input and the public key corresponding to the private key used by the signer to the RSASSA-PKCS1-V1_5-VERIFY algorithm using SHA-256 as the hash function.
  3. If the validation fails, the signed content MUST be rejected.

I realize that step two makes absolutely no sense to anyone who doesn’t work with cryptographic algorithms for a living. Fortunately, my partner does exactly that, and it made understanding this process way, way easier. Let’s take a look at the first step, though.

Decoding the Signature For Validation

If you try to decode a string of Base64-encoded text using the function I posted in part two, you’re going to get a big fat error. Silly me for even trying! Base64URL encoding is different from Base64 – it replaces the special characters + and / with and _, in order to make the string URL-friendly. Not only that, but the C# cryptographic classes we’ll be using actually need the data in a byte array, so there’s no point in going to the trouble to convert it to a string.

This is all you need to do to decode the signature:

  1. Swap the URL-friendly characters for Base64-compliant ones.
  2. Pad the string with = if necessary.
  3. Convert the string into a byte array.

That wasn’t so hard! Now we can just use this function to convert our signature. At the same time, we need to convert the first two concatenated JWT segments into a byte array, because that’s what the .NET X509 certificate library expects.

And just like that, we’re done with step one!

Verifying the Signature Using RSA PKCS#1

I have a confession to make. I spent a long time – several days, in fact – trying to use various bits of .NET’s RSA libraries to do the actual signature validation. I got more than a little frustrated and probably cursed at least twice at my laptop.

Then, a messenger from above came to me in a dream and gave me everything I needed to know. Seriously.

Nah, what actually happened was that my hours of Googling finally proved fruitful, and I found sample code on MSDN that did exactly what I needed to do. This example is almost verbatim what I used.

To start, we need to take our locally-cached certificate, which is stored as plain text, and convert it into an X509Certificate object. This will allow us to read the various properties of the certificate, much like how you can view certificate details in Windows’ local certificate store. Because the original X509Certificate class was very limited, Microsoft later expanded it with a subclass titled X509Certificate2. We need some specific details of our certificate that are only available in this class, so we’re going to convert it twice.

Again, we’ll use the key ID, or kid, to pull the appropriate certificate.

The actual certificate we’re going to use is gcert2. The RSACryptoServiceProvider class provides encryption and decryption functionality using the RSA algorithm. Since the JWT signature is encrypted with Google’s private key, we can use Google’s public key, which is stored in our certificate, to decrypt it. This takes the PublicKey parameter of our X509 certificate and converts it for use with the other RSA functions we need.

Since the decrypted signature is a SHA-256 hash, we need to generate the hash we’ll be comparing against the decrypted signature. To do this we’ll just hash the byte array we created earlier named data.

Next, we’ll use the RSAPKCS1SignatureDeformatter class with our public key. This class does the actual signature verification, comparing the signed hash with the hashed JWT header and payload.

Finally, the actual verification will return TRUE or FALSE to our login function.

Just to put together everything, here’s how you should use this signature verification process with the payload in Google’s original JWT:

Obviously, this doesn’t do anything to add the user to our application’s local database. I haven’t gotten that far. At this point, however, we’ve taken all the measures necessary to verify the integrity of a user’s Google account information and have assurance that the user is who they say they are.

The Big Conclusion

Google’s caution against using custom code is not without reason. Most web developers aren’t going to understand enough about cryptography to know what needs to be done to ensure end-to-end integrity when using a third-party service for logging in. I had the distinct advantage of knowing a cryptography and security professional who was able to go through all the documentation with me and explain what each security term meant and what I needed to figure out how to do it in C#.

If you’re still not comfortable with using your own code like this, that’s okay – there’s nothing wrong with using third party libraries. In fact, the MVC framework that ships with Visual Studio Express for Web includes the DotNetOpenAuth library for OpenID and OAuth2 authentication from a variety of popular service providers. I’m old-school, though, and wanted to do it myself if at all possible. I don’t have any assurance that the DotNetOpenAuth library will continue to be actively maintained and expanded, especially considering that the lead developer stepped down several months ago. More importantly, Google rolled out OAuth2 support nearly two and a half years ago, and the most popular ASP.NET C# library for this functionality still hasn’t been updated to support it. It still uses the old OpenID API, which I suspect will eventually be retired by Google.

It just depends on your needs and what you want to accomplish. I found this whole exercise to be very enlightening, as I now have a much deeper understanding of how OpenID and OAuth work. Hopefully, reusing this code for other providers should be relatively trivial. If not, at least I’ll have something else to write about!

Part one of this series is available here.
Part two of this series is available here.

4 thoughts on “Validating Integrity: Google Authentication Using OAuth2 and ASP.NET C#

  1. Pingback: A Better Geek :: The Big Picture: Google Authentication Using OAuth2 and ASP.NET C#

  2. Marcel Korpel

    Thanks for this very useful series.

    Unfortunately, neither Facebook nor Microsoft’s Live service send back a (signed) JWT. The only thing I can do is ask for the user’s info, which at least contains an id (in case of Google, this is the same unique id the sub field contains) and hope the https connection isn’t compromised.

  3. Claire Post author

    Marcel – Thank you for pointing this out. I recently started looking at the documentation for Microsoft’s OAuth2 implementation but hadn’t had a chance to get into it in-depth.

    It’s really unfortunate that Facebook and Microsoft don’t use a signed JWT. Hopefully that’s in the pipeline for both of those services – verifying signed data is a much more secure way of preventing man-in-the-middle attacks and other compromises.


Leave a Reply

Your email address will not be published. Required fields are marked *