 Let me get started, people can come in from the hallway if they're still out. My name is Lance, if you're here, you probably wanna learn about JSON web tokens, so you're in the right place. We're gonna talk a little bit about what they are, why they exist, and maybe who cares. So, they're tokens, they use JSON, and they're built for the web. Nevermind, scratch that, now that you're on the room. I'd like to offer you a special invite, ground floor of mine. So, I've got this idea, it's gonna be an application. You're gonna log in and you're gonna see a face. Probably your face. It's gonna fit somewhere in this like, faces as a service vertical. I think it's gonna be pretty big. I mean, the face is gonna be pretty big, clearly. I'm calling it face page. So welcome, you're all hired. Oh, you didn't know this was an interview. I mean, you passed the code challenge when you registered for the conference, right? So we're good. And you all have faces, so the qualifications are there. So let's go. Oh, I suppose you're wondering who you'll be working with. I'm Lance Ivy. I'm based out of Portland, Oregon. So if I seem a little out of sorts, it's because I'm trying to get used to the bright solar radiation. I started professionally tinkering on the web as an unpaid developer in 2003. Joined some friends and colleagues from college on this startup idea in a small town of like 30,000 people. And started in front end, and the era of Netscape had just wrapped up. So I like opened it once, took a picture of a horribly broken rendering and made some nice tables after that. Some things happened in 2006, 2008. I became a paid Rails developer. Found my way to working on Kickstarter. Made my own share of mistakes, learned from most of them, picked up a bunch of stories. Currently, I'm working with EmpatiCo, and also building an open source project called Carrots and Offend, which is an authentication server. You can imagine a device that we're rebuilt today as a standalone service. And this is the primary learning opportunity for the talk. So let's get back to face page. Obviously, we're gonna run face page as a standard Rails application. So, here we go. We have a few meetings. We pivot, we sprint. We change task management systems. We iterate on faces and what they mean to our users and how they're gonna change the world. And then, we deploy it. It's out in the wild. Users love it. There's just one problem. People like seeing their faces on the go, and they're looking for us in the native app stores. So, easy, right? We make an API. We build out our iOS, Android apps. And maybe the app and the API are intertwined. JSON endpoints in the same controller. Or maybe they're namespace, but they're still in the same app. It's a majestic model of life. Or maybe it's a standalone deployment, but it doesn't matter. What matters is it's up in the cloud, right? It's just, it's there, it's running, it works. But something along the way has happened. We couldn't use cookies for our API. So, we implemented a quick token authentication system. So now, we've got cookies over here, tokens over there, and this uneasy feeling that there's something similar, but we can't quite put our finger on it. And we just keep going. But it turns out that we've actually implemented two authentication systems. And they're separate. So this is technical debt, but we figure we can manage. It's working, we keep moving, and just, you know, suppress those uneasy feelings into a tight little ball. Then someone on high maybe makes the decision to move to services, which, I don't know. Try it out, and we build this API gateway. But is it written in Rails? Does it speak cookies or tokens or both? Do we have to retrofit our authentication schemes into it? Are we on our own? Do we have help? Or something else? I mean, what happens now? Well, I want to take a step back and consider the problem that these cookies and tokens are actually trying to solve. How do we even get here? It's time to ask, what even is logging in? What happens? What are the cookies and tokens doing? Let's dig in. We can say it starts here with a username and password. These days, it could also be Facebook, or GitHub, or Google, or some other auth provider, but probably still at least passwords. Turns out that's not really the part we care about. What we care about is how we keep track. So here's the classic way of keeping track. Browser logs in, we send back a cookie. And browsers know about cookies. They know to include them on every request back to our domain. So every request for a face on a page is logged in. And we can show your face back. I mean, the requests that want JavaScript and CSS and images are also carrying these cookies, but that's a different talk, right? The simplest explanation for cookies is a header protocol. When the server responds with a set cookie header, the browser knows to include it back in the cookie header. Every future request. But I think it's time to consider the elephant in the room here. What kinds of cookies are these, really? I mean, when we diagram HTTP cookies, they usually look like this. Chocolate chip or raisin, maybe. Oh, what's for lunch? But this is how we should think of them, as fortune cookies with a message inside. So Rails uses these to store bits of data in the browser, and we can crack one open and see what's inside. Here's one that you might actually see in an actual Rails application. It's an encoded message with a signature. And you can split it apart on the double hyphens right there. Here's what's inside. There's a user ID, there's a CSRF token, because with cookie authentication, you need that kind of thing. And there's a signature that we can use to just make sure that it's legit. So, there we go. Cookies are headers on HTTP responses and requests that do things like transport a user ID back and forth. And that's the login story we care about right now. So what's happening with the tokens on the API side of things? Well, one common convention might look like this. The server responds to a login request with some random string in the JSON body, let's say. And again, the device sends it back on future requests, but this time in the authorization header. Now, these tokens are opaque. They're random strings. They have no meaning until we use them to find something more interesting like the user ID. And this is good, but it's not great. On the upside, like we could delete these tokens to revoke access at any point. We have some control over that. On the downside, every API query or now involves a database query. This is, by the way, how Rails sessions used to work before switching to cookies. And it was a performance problem. The browser submitted a session ID and you use that to actually find their session from the database. All right, so let's put together what we've learned. The Rails session cookie uses a cookie header, but our API tokens use the authorization header. The Rails session cookie is structured data, but the API is just an opaque random string. Now, the Rails session cookie can be verified with cryptography. The API token is security through queries. So can you imagine the best of both worlds? Chase on web tokens. It's signed structured data. This is rather similar to Rails signed cookies. We've added a third segment, the header, and this just describes the format of the token. We can decode the message. What you're looking at here are called the token claims. Now they're called claims to give you a bit of skepticism because you actually have to assert that these claims are true before you can trust it. Here's a list of common claims. You can see that they're heavily abbreviated. And the reason is because JSON web tokens are designed to fit in headers and other character-limited small spaces, so they have to save on bytes for your more interesting content. All the claims on the left are what I consider metadata. Their claims necessary to verify that a token may be used appropriately in different situations, and I'll just go through these. The issuer describes the party that generated and signed the token. Audience describes the party that the message is intended for. These might be the same thing, it might not. Issued at is when the token was created, and expiration is when the token should be ignored. They can have a lifetime. The claims on the right are what I consider the payload, and this is the information that you probably want to extract for your business logic. Now you can put anything you want in here as long as the issuer and the audience agree on what it means. But the common one that a lot of issuers and audiences agree on is the subject, and this is meant to identify the party that the message is about, or the person who owns the token, the person who has it, and this is where we put the user ID. So the JSON web token standard is a pretty generic thing. It's just a spec for sending secure messages, but one of its primary uses is identity. It actually evolved in the context of OAuth and OpenID Connect, and you can see a lot of that in the claims that are built into it. So we can actually imagine it kind of like Rails Cookies and API tokens. Think of it as an ID card. Just like an ID card, it makes a number of claims, and it contains some security features. This ID card has an issuer, it's from the internet, it has a subject, name, has expiration and issued dates, and it has this pretty sweet little official stamp. Security. But it's actually up to you to check the card and detect forgeries to make sure that you can actually take this identification. So here's how you do it. One, is it from someone that we recognize as an authority? Check the issuer. Two, was it intended for me? Check the audience. Has it expired? Check the expiration. Is it a forgery? Check the signature. Can you recreate the signature based on those values? And last but not least, was it generated before or after that time we had to change our secret because we published it on GitHub? And you can check issued at for that. If you can answer those five questions, you're in pretty good shape. And the good news is that you can get a library to just do this for you. Okay, so we've learned that JSON web tokens are secure messages, like a Rails signed session cookie. We've learned that they contain claims that we need to verify, and we've learned about the most important claims and what they represent. So let's talk about what we can do with them. We've already mentioned identity tokens, so let's continue from there. One of the problems we had in our face page app was the duplicate authentication system. So let's see how JSON web tokens can help. First, let's add JSON web tokens to our comparison table. So while the Rails cookies are tied to the cookie header and the API tokens use the authorization header, our JSON web tokens are ready for either. They don't care. The Rails cookies were structured data signed with cryptography, and that's actually pretty good. So our JSON web tokens share those characteristics. So if we use an identity JSON web token for login, it might look like this. Browser submits the login. Rails responds with a JSON web token in a cookie. The JWT contains the user ID as the subject. Now in future requests, the browser just sends it back in the cookie. And this looks pretty familiar. And that's a good thing. We actually haven't changed our headers or the relationship between the browser and the server. We're still using cookies. We've just changed the format of the message inside the cookie. And on the API, we can drop it in here as well. There's no change to the client. They're still just sending a string back and forth. But now the string is a JSON web token. It has structured data. It has meaning. It's not random. And the server can do something with it. So this is the JSON web token solution. One token, two headers, one authentication system. It doesn't matter whether the server finds the token inside a cookie or inside an authorization header, it can still handle that value exactly the same and set the current user for the duration of the request. Problem number two in our face page app. Previously, the API had to execute a query on every request just to discover who was making it. And now our API can verify the JWT with the claims and the cryptography. So this replaces a network-bound database bottleneck process with a straightforward CPU-bound calculation. This will perform faster. This will scale better. This will introduce less variation into response times and generally just have fewer failure modes. Problem number three. Our cookies were implemented for Rails by Rails. Now, don't get me wrong. The default Rails session store is wonderful. It works, it's hidden, it is secure, it is very well-designed, and it does the job it needs to do. But it is tightly coupled to Rails, it is tightly coupled to cookies, and it's kind of tightly coupled to majestic monoliths. So if any of those things don't work for you, you have to ask yourself, what's next? JSON web token libraries are implemented in at least 20 languages. They're decoupled from the cookies. And they contain claims that you can use to build any kind of distributed architecture. So they're more flexible. It's a more general-purpose solution. Problem number four. In a distributed architecture, you might find yourself sharing secrets so that when you sign a message with one system, you can verify it on another. And this involves trusted back channels, like copy and paste. Or configuration management systems, and now that the secret exists in more places, it's a bigger attack surface. If any one place is compromised, that secret can be used to attack all the other places as well. So what do JSON web tokens offer? They support asymmetric key algorithms like RSA. Now the signature process used by Rails to sign a cookie is called HMAC. You give it a salt, it hashes the cookie. You take that salt, you rehash it later, and it verifies. Now the required setup for RSA is a little bit different. The server signing the key, signing the token needs a special RSA key, not just a random salt. It uses the private key to sign the token, but then it can actually publish the public key on a free and open HTTP endpoint using a spec like JSON web keys. When some audience, some other server, receives the message, what it can do is go fetch the public key, use that to verify, and then cache that forever. So one HTTP call automatically shares the secret, but it's not a secret, it's the public key. So this investment means that you don't need to share secrets. It's a little bit more upfront than the operational cost is lower. This means there's no copy and paste between the systems, you just fetch the key over HTTP. There's no super coordinated lockstep deploy process when you need to change it on both places at the same time without dropping messages in the middle. This also reduces the attack surface. A lost secret can only attack the service that leaked it. This can actually even prepare you for some really nifty automatic key rotation stuff. I'm not gonna get into that right now, but if you're curious how that works, happy to talk about it, come find me around the conference. Problem number five. You thought you knew about how password resets worked. You generate and send a token. It's a random string. You verify it in your controller, and then you regenerate a new token to expire the old ones that you sent out and make sure that people can't hack the system. And when you think about it, this is actually a third authentication system. It works a lot like the opaque API tokens. And again, it's implemented as a one-off. So here's how I build a password reset JSON web token. First, start with a standard identity token. This contains the metadata claims and the subject. Then I add a scope claim. Now I looked around, this one doesn't seem to be standard, and I couldn't find a better one, so I'm just calling it scope. The idea here is that I'll configure my passwords controller to accept tokens with this scope, and I'll configure the rest of my application to reject tokens with this scope or any other scope that doesn't seem appropriate for them. Then I add an optimistic lock. Now an optimistic lock is where you keep some kind of version. And every time a field updates, you increment that version. Anyone who wants to make a change has to tell you what the current version is. This way you make sure that they don't overwrite something that changed without their knowledge. And you can achieve this with just using a timestamp. So that's what I've done here. I'm maintaining a user's password changed at field, which is also good for other features, and then verify it against the token. If it matches, it's a go. And this effectively has the same, this effectively also expires the old reset tokens as soon as the password changes. So here we go. Once again, we can upgrade opaque tokens into structured signed data. This is one less field on the user's table. This is one less index for your queries. But even better is that we've absorbed a third authentication system by teaching our JSON web token backend about scopes and by teaching our password controller about optimistic locks. Problem number six. Suppose that you're sending emails with survey links or some other kind of strong call to action where you need the user to click and you need to know who is making that click. So if you don't help them at all, they're gonna hit a login wall and maybe they're on their phone so they don't wanna type it in and they just come back later or they don't and your conversion drops. So maybe you implement random strings, opaque tokens, and you connect them back to the user just like the API system. I mean, this is sounding pretty familiar, right? So we can just generalize the password reset solution. All we need is a scope claim. If it sounds like I'm suggesting that we send user sessions through email, yeah, actually, that's exactly what I'm suggesting. I mean, that's basically what the randomly generated one-off random strings are doing. They're giving a login session by email. But this one is built into our authentication system. It's not a one-off implementation that you're gonna forget about. All right, problem number seven. Your application is already doing lots of stuff and you're including a lot of this common standard authentication feature stuff as if it's somehow unique. They run from the same database. They can be affected by every deploy, every upgrade, and all this complexity is in one spot which makes it a lot harder to audit your attack surface and understand where you need to remain secure. And let's not forget the always-present user God model. And JSON web tokens can help. So they were born ready for this. This is why the issuer and audience claims exist so they can be different things. And the issuer, let's imagine, takes responsibility for the account and the account might be the username and the password and the last time the password was changed and how many times they failed their login or any of that stuff. And this leaves your application responsible for a simpler user's model. It just needs to relate the user with an account. And this is actually what I learned firsthand while working on Keratin Authent which is kind of what you get if device was rebuilt as a standalone authentication service. It removes the complexity from your app, relies heavily on JSON web tokens, uses every trick I've mentioned here, and then some. The core tech is pretty stable, I think, and I've got some ideas for some advanced features. So if this is interesting to anybody, I'd love to chat about it. All right, in conclusion, these are the things I hope you take away from the presentation. One, you can use JSON web tokens right now. It doesn't matter if you have a monolith or services, you probably recognized one or more of these problems. Two, JSON web tokens have a low learning curve and a high skill ceiling. There's a lot more that you can do with them as you gain confidence. But all you need to do is start somewhere. So you're probably gonna go home from this conference with a head full of ideas for things that you wish you could use at your day job and all this cool stuff that you wanna try. And maybe JSON web tokens is one of those. Pick something, learn it by doing it. Try it out and make your knowledge real. And we do have time for questions. So the question is if the user has been disabled by an admin since they logged in, how can you immediately log them out and make sure that they don't come back? You can create, for example, a blacklist and keep this temporary cache of invalidated tokens. If you choose to for critical actions, implement a revalidation. But yeah, the trade off is real. The trade off is real. If you keep authentication in a token that lives for 30 minutes or something, that's the other thing. I do recommend that these tokens have a short life and that you regenerate them frequently. So the time window for that kind of problem is small. Yeah, so again, the comment is that the checking this blacklist takes time. So I recommend deciding which endpoints are critical and revalidating for just those endpoints. There are probably a lot of things where it's okay if they can continue to read data for a few more minutes or maybe what you wanna do is protect that data and you can choose to revalidate where necessary. Well, you can try JSON web tokens in an existing app by finding any kind of opaque token and seeing how you can replace it. So that's one way to try it out. If you wanna try an authentication server, then it helps to have an app with some kind of login scheme where you just swap out where the form submits. So the form might submit to your backend and now the form submits to a different backend. Those are two ideas. Is there a drop-in replacement? There's a library called knock and knock is like devised with JWT but it's built into the monolith, right? So an authentication server is gonna run as a separate deployment but knock will use JWT as part of the same monolith. Refreshing the tokens with more tokens. So the way that I've seen is where you maintain what's called an access token and a refresh token and the access token is what you're sending to the API. The refresh token is more secure because it's not used for very much. The one thing it's used for is getting new access tokens. So let's say that your access token is good for an hour and let's say that you decide every 30 minutes way before that's gonna expire, you use the refresh token to get a new one. What you can do is build revocation into that refresh system. So the refresh maybe actually does a Redis query or a MySQL query or something to make sure that it's still okay to generate new access tokens. The dual token system means that one can have different security properties and the other is used for your really chatty protocols because it's lightweight and doesn't require queries. And they're kept in different places with different security properties. Yeah, so the comment or the idea was that if you attempt to use a token and the error is that it's expired, you might decide it's time to get a new one. And that's definitely like the fail safe. I think that aggressively refreshing them before that happens is a cleaner experience. And if you refresh well enough, you may or may not need that kind of last moment it's expired, let's get a new one thing. Yeah, that's the issue at check. So in that kind of dire situation, which we all hope isn't happening, there's really no going back. You generate a new key and start using it. And then what you do is you roll out this idea of an epoch and you say any token generated before this point in time was using an old key that we don't trust anymore. So if the issue that is before last Friday when this happened, then just throw it away and that means people are logged out. Oh, so the comment was that you might have multiple keys and so you need an epoch per key. JSON Web Tokens has a claim called key ID. So you can embed within each JWT a signature of the key that was being used. And that will let you, if you have per key revocation tell exactly which one was being used and whether it's still trustworthy. For simple login for sessions, it's just what you saw. It's the user ID. If you choose to put them in a cookie, then you need to also figure out CSRF. And you can put that CSRF token in the JWT because it's open, like you can add more stuff into it and solve those same problems. I'm actually not a big fan of keeping a lot of data in sessions anyway. So I wanna say on the slide, it was like two or three lines. It was maybe 50% longer than a Rails equivalent cookie. Some people advocate for keeping more user information in there, like are they an admin? What permissions do they have? What's their name and email? So you can save more queries that way. The downside is, as mentioned earlier, you have to decide how much you care about that information getting stale. All right, I think we're good.