 Welcome to JSON Web Tokens, Understanding Common Pitfalls. My name's Bruce. You may recognize me from the show floor as the guy that's overly enthusiastically handing out info stickers. I'm actually a software developer. My background is in web development, application security, and access management. So in those roles, I've spent a lot of time reading specifications for the web, such as the JSON Web Token RFC. And today, I hope to distill some of that knowledge and bring it to you. So this is a 101-track lecture, which means I'd like to start from a base foundational knowledge to get us all on the same page first. But if you've worked with JSON Web Tokens before, then this might be a good review. Once you have them implemented, you generally just start relying on them and trusting that they're working as expected. So this might also give you a way to go back and make sure that you're actually using JSON Web Token securely. Starting from the base, we're going to be going over a JSON Web Token format, so what they actually look like and what's inside of them, along with how they're actually used. Then we're going to move on to signing JSON Web Tokens. This is the key and bit of information that I want to make sure everybody can get on the same page for, because the signature is the key to security for the JSON Web Token. If your signature in validation is not good, then pretty much all bets are off. Anybody can tell anything to your applications and they will trust those tokens. The signature is important because there's information encoded in the JSON Web Token that whoever's receiving the token will look at and take us true based off of this signature. And then finally, once we understand that signature, then we'll move on to the security and common attacks on JSON Web Tokens. By the end of this talk, you should be able to accept JSON Web Tokens, create JSON Web Tokens, validate them, and identify some common vulnerabilities. I like to start things off with some real world analogies before we actually dig down into the technical details. Anybody here actually go to the bar last night after the conference? I know I did. And the first thing that happens when I went to the bar, I guess I have a youthful glow, is they asked for my ID. So I produced this government ID and gave it to the bartender. The first thing the bartender did was look to see that the ID was valid, issued by somebody they trusted, which was the Canadian government. If it looked like it had all the anti-forgery features that an ID has, and finally, once they trusted it, they read the information on the ID to make sure that I was actually who I say I am and I can buy alcohol. Finally, once they validated that information was correct, then they served me. This is actually pretty similar to the process of validating a JSON Web Token with an application. So I was able to validate who I was at the bar using my ID. But how am I going to do that online? Obviously, I can't scan my passport and send it along to every website. That would be inconvenience and extremely insecure. I need some kind of virtual piece of ID that's issued by a trusted authority. But maybe in this case, not the government's. That also wouldn't scale very well. So who do we trust online? We trust the big tech companies like Google or Microsoft. So maybe we can use them to sign in, or maybe even on a smaller scale if we have our own microservices we're using. Maybe we have an authentication service that we trust that our other microservices can rely on. This is where JSON Web Tokens come in. They're like a digital online piece of identification. So going back to our bar analogy, when I presented the bartender with the ID, that's like the JSON Web Token itself. When the bartender looked at the JSON Web Token and saw that it was issued by the Canadian government, that was like seeing that the JSON Web Token was signed by a trusted authority like Google or your authentication service. Finally, they read the information from the ID or token and made some assumptions about who I was because they trusted it. So what is in a JSON Web Token or JWT? Well, it's an acronym, and you can break it down to its three parts pretty straightforwardly. The first part is JSON, or JavaScript Object Notation. If you've done web development, I'm sure this looks familiar. It's a pretty standard way to send information about an object between a server and a client in a way that's easily readable. The next part, W for web, this is the expected place that JSON Web Tokens are to be used, normally between microservices or any other application running on the web. And finally, there's T for token. And this isn't a token in the sense like a Bitcoin or any other kind of cryptocurrency. This is just token in the form of a piece of information that asserts something about the person that holds that token. So this is what a JSON Web Token actually looks like. It's pretty esoteric and intimidating to humans, pretty clearly not meant to be read by us. And if you look closer, even though it's intimidating with a bunch of numbers and letters, you'll see there's actually three dots separating distinct parts of this JSON Web Token. So what we're actually looking at is the three different components of a JSON Web Token, the header, the blue part, the payload in pink, which is the information about who holds this JSON Web Token, and the green part is the signature. One important thing to note is that although this looks very obscured and potentially secure, it's actually just base64 encoded. Base64 encoding is just a standard way to encode information to send it over the wire. So a common misconception I guess I've seen when people are working with JSON Web Tokens is that they think they can store secret information in them, and in the case of a standard JSON Web Token, that is not the case. It's just base64 encoded, so whoever receives this token can decode it and read whatever's inside of it. There is something called an encrypted JSON Web Token, which I'm not gonna talk about here, but if you're looking to send encrypted secret information to somebody, that's what you'll want to use instead. So let's go through the components. The first component of the JSON Web Token is the header. So this is information about how the token should actually be validated by the party that receives the token. In this case, you can see that it's specifying the HS256 algorithm was used to create the signature, and in the case of all JSON Web Tokens, as far as I know, the type will be Jot. There can be other things here if it's not a JSON Web Token. The next part of the token is the payload. This is the meaty bit that your clients will actually be looking at. This contains all the information about the party that is presenting the token. So this could be a token presented by a user, or it could also be a token presented by, say a machine, like another microservice calling it to a different microservice. In our example here, we've got the email of the user that the token was created for, hello at example.com, and then two other commonly seen fields in JSON Web Tokens. So these are abbreviated for the point of just saving space in the JSON Web Token, but you can see the second field there is IAT, which stands for issued at. This is the time the token was created, and if you receive a token with an issued at in the future, it's not valid. And the third field, which is also an extremely important one, is EXP, the expiry of the token. These are both UNIX timestamps. The expiry is important, because if you receive a token that's after the expiry, the information in it may no longer be valid. So kind of going back to the ID analogy from earlier, this is like the information that was written on your driver's license or passport. And for the expiry, that's like the expiry of the passport, like maybe somebody's address has changed, or maybe some of their features have changed since this passport was issued. And finally, there's the signature there. This part is kind of a little bit more difficult to demonstrate because it doesn't decode into JSON. It's just an integrity check value for that header and payload. Like I said, this is actually the most important component because it lets us know that the information in those first two sections has not been tampered with, and we can trust it. So let's break down actually how we get this signature to make sure that we're doing it correctly and validating it correctly. Well, there's two ways to actually get the token signature. The first way is using symmetric key signing. So according to my research, this is actually the most common way to sign a JSON web token. In my experience, symmetric key signing has been more popular, but it's pretty straightforward. So when the party is creating a JSON web token, they'll take that header and payload. So the information about how the token was signed and the information about who the token was for, and they'll generate a hashed-based message authentication code, which is a mouthful, using the header and payload, along with a shared secret key. This outputs a unique value, which they set as that third section of the JSON web token. One important thing to note here is that you don't really have to worry about how hash-based message authentication codes are working just know that when you pass in this shared secret key, which is like a password, it's going to output some unique value. And if anything in the payload of the token changes, like say somebody decodes the token, changes their email to admin at example.com, then this signature will no longer be valid and the signing party will reject the token. So how, sorry, the receiving party will reject the token. So what does that actually look like? So the party receiving the token will follow a similar process. They'll take the header and payload values, and they'll also use this shared secret key, which they've established somehow beforehand to exchange with the other party, and then they'll find the unique value and compare it to the signature sent in. Since these signatures match, they'll know the token is valid and they can proceed safely. Kind of the downside of this symmetric signing approach here is that although it is pretty straightforward using the shared secret key, establishing that shared secret key is a little bit less secure. You don't know who all has access to it. So since the person validating the key has that same password secret key, they could also generate tokens that would be seen as valid. And if there's other people that are validating those tokens, then you can't actually tell who it came from since the key is distributed. Due to this, I've used asymmetric signing more. This is a good approach if you've got a bunch of microservices and you've got one service that's only focused on authentication. You don't want the other services to be able to create their own tokens at all. So asymmetric signing is like how cryptocurrency, while it works, you've got a private key and a public key, and the private key is only held by the authentication service. Meanwhile, the public key is available on some endpoint for anybody to access. It's public intentionally and can be used to validate signatures created with this private key. So here's what it looks like to actually create a signature for a JSON web token using asymmetric signing. Once again, we take the header and payload and we hash them, which creates a unique value. Then, since we're the party that's creating this token, we can use our private key to encrypt that unique value and add it as the signature. When somebody receives the token and they need to validate it, they will use the public key of whoever signed that token to decrypt that third section of the token, the signature. This will reveal what the person that created the token says the unique value was and then they can compute the hash of the header and payload and compare them to make sure that nothing has changed. Due to the nature of asymmetric keys, only the person that holds that private key would be able to generate that signature so you know that it's valid. So what does this look like in practice? This is a super simple diagram of what a microservice architecture might look like. You can see on the left here, we've got the authentication service and on the right is some other service. Let's say it sends emails or something. It could really do anything. So step one, the clients, the laptop down there will log into the authentication service, say with the username and password. The authentication service will validate that username and password and then create the token for that client and sign it with their private key. The tokens then send back to the client and they can make requests to the email service for whatever feature they want. Say they call an endpoint to send an email, they'll send along that token in the header of the request and the other service can then take that token out of the header. Look at the public key for the authentication service which is public and everybody knows. Validate the signature and that the contents of the token haven't changed. Validate the tokens and how it expired and then finally proceed to do whatever it wants. Send the email. So let's break that down into an algorithm. So what we did there was we've separated the JSON web token into its three segments. Header, payload, and signature. We've decoded the header and payload and checked from the header which signature algorithm we should use to verify the token. Then, based on that algorithm, we've verified that the signed hash matches the header and payload, the hash of the header and payload of the token that we've received. Finally, we know that's valid so we checked the information. Who is this user? Is the token expired? Is it after the time the token was issued at? And then we can proceed. Seems pretty solid, right? Well, there's one fatal flaw in here that's actually enabled by the JSON web token specification and that's step three. So when you're validating the header segments of the JSON web token, the RFC actually says a none algorithm is valid to use. So what this means is whoever's receiving the token, we'll check the header to see which algorithm they should use to validate the signature. And in the case of none, that means don't validate this token, just take it as valid. So this is actually a pretty big footgun if you're just immediately writing to the spec of the JSON web tokens. And it might seem extremely obvious, but this is actually something that's fairly common. In 2019, I don't know if you've heard of Auth0. They're a fairly prominent Oauth sign in service. They were actually vulnerable to this signature algorithm confusion. And that meant that any token that was sent in with the none algorithm in a different casing in their case, I guess they were comparing the actual none capitalized to a different casing was seen as valid. So how do we protect against this? Well, it's pretty straightforward as we go off of the spec. Actually, here's a demonstration actually of what it would look like to attack a service that's not validating the algorithm is what they expect. I'm running commands in the terminal here. I'm just creating a header here with the none algorithm for signing the token and base 64 and coding it. Then I'm creating the body of the JSON web token, setting the email to whatever I want. In this case, admin.example.com. And since it's not being validated at all, the service accepts that as correct and allows me to proceed as admin.example.com. So yeah, we can protect against this by going off the spec and only allowing specific signature algorithms that we accept as valid. So in our case, let's make sure that the token is using an RS256 signature. Most JSON web token libraries will protect you against this and just decline the none algorithm. But it's worth checking the library using and making sure that's actually the case. The next vulnerability is in the case of symmetric key signing. So as I pointed out, that secret key is the keys to the kingdom. And if that's compromised, then any token that comes into your service could potentially be not correct. So in my example here, I've got a token that was signed using a symmetric key. It was a really short one, SN1F. I found a random tool on GitHub called Jotcrack, which just brute forces the secret key used to sign a symmetric key signed token. And I ran it here against this token and my MacBook was able to crack it in 10 seconds. So once a party has access to your secret key, they can use any legitimate tool like Jot.io. This is just for decoding tokens and modifying them. And plug in that secret key and enter whatever information they want. So you can see here, it might be a little bit small. I set my name to admin at example.com and I plugged in that secret key and it was valid. So anybody that passed that token into our service would be able to make whatever calls they wanted as an admin. The protection against this is pretty straightforward. You're just gonna wanna use a sufficiently long secret key. The length of that secret key varies depending on what signing algorithm you're using. Kind of building on that a less direct but increasingly common attack that I've personally seen growing in the wild is that attackers are now going directly after tokens rather than username and passwords. And the reason for this is that as NFA is getting more popular, if you have a username and password, then you still need access to whatever the second form of authentication is. So maybe you have to like SIM jack them and get access to their actual phone number to do like a text verification or something. But as an attacker, if you can get access to the tokens, then you can bypass the whole initial authentication flow and just make requests until that token expires. So this is a pretty tempting target for a user or for an attacker, sorry. So you're gonna wanna make sure you store these JSON web tokens securely. In my case, I prefer storing JSON web tokens in a cookie for the user experience. But this comes with some pitfalls that I wanted to point out as well. So if you're storing the JSON web token in a cookie, you're gonna wanna make sure that cookie is secure. That means using an HTTP only cookie which can only be sent, or sorry, HTTP only cookie, which can only be accessed when sending requests. It can't be accessed by JavaScript, which means no malicious scripts in the browser can actually access that cookie. You're also going to wanna make sure the cookie is set to secure. So it's only sent over HTTPS. Nobody intercepting the traffic will be able to read the cookie in that case. And finally, if you're not doing some fancy redirects for a single sign-on, then you'll also wanna set a same-site flag on the cookie, probably too secure, so that it can't be sent across redirects. The other option is to actually not store cookies at all. This means just keeping the JSON web token in memory while running your application. But that's a little bit of a trade-off for user experience, because if they refresh the page, they'll log back in. As you saw in both the signature algorithm confusion and secret brute-forcing attacks, the most important thing is definitely storing your keys securely that you're using to sign these tokens. The best thing to do is to store the key in a place that it can't be accessed at all, like a hardware-signed hardware certificate manager or something like that, like AWS HSM. I'd recommend something like AWS key management, so only the parties that are expected to be able to access the key can access it, or something like vaults. So that's more auditable. Anybody calling in to access your keys, you can see who's using them. And in the unfortunate case that your secret key actually is compromised, it's much easier to roll it. It's contained only in one place. You can go change it there rather than finding everywhere the secret key is just stored in the environments and going and change them individually. So we've gone through the main security pitfalls, but I think another common pitfall that I wanted to point out was making sure you're actually using JSON web tokens in a way that makes sense. So JSON web tokens really shine in a distributed environment. They're stateless, which means you don't have to call in to some central authentication service to make sure they're valid. And this is good for if you're making calls to many like distributed systems. They don't have to call back always to validate the token. They know the public key so they can validate what it says is true. This will reduce the network calls, reduce latency. And it's also nice just to be able to assert some information about whoever's calling your service by checking the token they used. But you might not even need a JSON web token. So if you're using just like a monolithic or a simple service, then session tokens might still be enough and you don't need to worry about any of this complicated stuff at all. So it's tried and true, just creating like a simple hashed secret to send back to the users. And then validating that when the request comes in if you have quick access to the database. The other kind of downfall of JSON web tokens that I didn't really touch on earlier was that due to their statelessness, if you need to invalidate them, then it can be a bit of a pain. So since you're accepting any token that signed is valid up until it expires, there's no way to revoke it unless you do some complicated stuff like a token block list. But that kind of defeats the purpose of using JSON web tokens. So that's another pitfall is that session tokens can be easily revoked. JSON web tokens might be valid for the duration of their lifetime. So you're gonna wanna make sure also to keep the session lifetime of the JSON web token as low as you can. So that's it. We have the base knowledge to create, verify, and securely work with JSON web tokens. You can go forth now and make sure that your libraries are actually validating JSON web tokens correctly and that you're creating them correctly. Thanks for coming up. If you'd like to talk with me about tokens at all, I'll be at the info booth at the conference floor. I'd love to hear from you. Thank you.