00:00:11.780
All right, good morning! Let me get started. People can come in from the hallway if they're still out there.
00:00:17.609
My name is Lance. If you're here, you probably want to learn about JSON Web Tokens, so you're in the right place. We're going to talk a little bit about what they are, why they exist, and maybe who cares.
00:00:28.619
So, they're tokens that use JSON, and they're really for the web. Never mind, scratch that.
00:00:36.149
Now that you're all in the room, I'd like to offer you a special invite to the ground floor of my startup.
00:00:42.030
I've got this idea called Facepage. It's an application where you log in and see a face—probably your own face. It's going to fit somewhere in this 'faces as a service' vertical, and I think it’s going to be pretty big.
00:00:54.780
Welcome! You're all hired. You didn't know this was the interview, but you passed the code challenge when you registered for the conference, right? So, we're good.
00:01:01.070
You all have faces, so the qualifications are there. Let's go! Oh, I suppose you're wondering who I am. I'm Lance Ivy, and I'm based out of Portland, Oregon.
00:01:15.310
I started professionally tinkering on the web as an unpaid developer in 2003. I joined some friends in college to work on a startup idea with a small amount of like 30,000 people, and I started with frontend development.
00:01:36.850
The era of Netscape had just wrapped up. I opened it once, took a picture of a horribly broken rendering, and never looked back. I made it a nice table after that.
00:01:49.989
In 2006, I became a paid Rails developer and worked on Kickstarter. I made my fair share of mistakes, learned from most of them, and picked up a bunch of stories.
00:02:05.200
Currently, I'm working with Inputy Co and also filming an open-source project called Carriage of Austin, which is an authentication server. You can imagine devices that were rebuilt today as a standalone service, and this is the primary learning opportunity for our talk today.
00:02:31.900
Let's get back to Facepage. Obviously, we're going to run Facepage as a standard Rails application. So here we go! We've had team meetings, we've done our sprint, we've changed our task management systems.
00:02:49.650
We've generated faces and discussed what they mean to our users and how they're going to change the world, and then we deploy it out into the wild.
00:03:03.630
There's this one problem: people want licensing events on the go, and they're looking for us in native app stores. So, it's easy, right? We make an API, build out our iOS and Android apps, and maybe the app and API are intertwined.
00:03:16.449
They could be JSON endpoints in the same controller, or maybe they're namespaced, but they're still the same path. You know it's a majestic monolith.
00:03:28.960
Or maybe it’s a standalone deployment, but it doesn't matter. What matters is, it's left in the cloud. It's there, it's running, and it works.
00:03:42.940
But something has happened along the way: we couldn't use cookies for our API, so we invented a quick token authentication system.
00:03:57.790
Now we've got cookies over here and tokens over there, and there's an uneasy feeling that there's something similar, but we can't quite put our finger on it. We keep going, but it turns out we've implemented two separate authentication systems.
00:04:10.810
This is a case of technical debt that we figure we can manage. It's working, so we keep moving and just suppress those uneasy feelings until they become more prominent.
00:04:29.650
That’s when management might make the decision to pivot towards services. We try it out and build this API gateway. But is it written in Rails? Does it handle cookies or tokens or both?
00:04:42.850
Do we have to retrofit our authentication schemes into it? Are we on our own? Do we have help? What happens now?
00:04:55.510
I want to step back and consider the problem that cookies and tokens are actually trying to solve. How did we even get here? It's time to ask: what even is logging in?
00:05:07.710
What happens? What are the cookies and tokens doing? Let's dig in! We could say it starts here—with a username and password.
00:05:29.740
These days, that could also be a Facebook, GitHub, Google, or some other authentication provider, but it probably still involves at least passwords.
00:05:45.449
Turns out, that's not really the part we care about. What we care about is how we keep track of logged-in users.
00:06:03.960
In the classic scenario, when a browser logs in, we send back a cookie. Browsers know about cookies; they know to include them in 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.
00:06:27.759
I mean, requests for JavaScript, CSS, and images are also carrying these cookies, but that's a different talk.
00:06:39.440
The simplest explanation for cookies is that they are headers in HTTP responses. When the server responds with a 'set-cookie' header, the browser knows to include it in every future request.
00:06:58.970
But I think it's time to consider the elephant in the room: what kinds of cookies are we really dealing with here? When we diagram cookies, they usually look like this: chocolate chip or raisin.
00:07:17.759
But this is how we should think of them: as fortune cookies with a message inside.
00:07:32.380
Rails uses cookies to store bits of data in the browser, and we can crack one open to see what's inside. Here's one that you might see in an actual Rails application. It's an encoded message with a signature.
00:07:49.260
You can split it apart on the double dash. What's inside? There's a user ID, a CSRF token (because with cookie authentication, you need that kind of thing), and a signature to ensure that it's legitimate.
00:08:01.240
So there we go. Cookies are headers in HTTP responses and requests that transport a user ID back and forth, and that's the login story we care about right now.
00:08:10.800
So what's happening with the tokens on the API side? One common convention might look like this: the server responds to a login request with some random string in the JSON body.
00:08:35.890
Let's say the device sends it back on future requests, but this time in the Authorization header. Now, these tokens are opaque; they're random strings with no meaning until we use them to find something more interesting, like the user ID.
00:08:53.820
This is good, but it's not great. On the upside, we could delete these tokens to revoke access at any point, which gives us some control.
00:09:11.950
On the downside, every API query now involves a database query. This, by the way, is how Rails sessions used to work before switching to cookies, and it created performance problems.
00:09:32.820
Browsers submitted a session ID to actually find their session in the database. All right, let's put together what we've learned.
00:09:50.690
The Rails session cookie uses the cookie header, but our API tokens use the authorization header. The Rails session cookie contains structured data, while the API uses just an opaque random string.
00:10:01.250
The Rails session cookie can be verified with cryptography, whereas an API token relies on security through queries.
00:10:12.290
So can you imagine the best of both worlds? JSON Web Tokens are signed structured data. This is rather similar to Rails signed cookies. We've added a third segment—the header—which describes the format of the token.
00:10:35.280
What you're looking at here are called the token claims. Now, I call these claims with a bit of skepticism because you actually have to assert that these claims are true before you can trust them.
00:10:51.290
Here's a list of common claims: the issuer, which describes the party that generated and signed the token; the audience, which describes the party that the message is intended for.
00:11:06.750
These might be the same thing, or they might not. Issued at—that is when the token was created—and expiration, which is when the token should be ignored.
00:11:16.590
Claims can have a lifetime. The claims on the right are what I consider the payload, which contains the information that you probably want to extract for your business logic.
00:11:28.070
You can put anything you want in here, as long as the issuer and the audience agree on its meaning. A common claim that a lot of issuers and audiences generally agree on is the subject.
00:11:41.150
This is meant to identify the party that the message is about or the person who owns the token—the one who has it. And this is where we typically put the user ID.
00:11:54.150
The JSON Web Token standard is a pretty generic thing. It's just the specification for sending secure messages, but one of its primary uses is identity.
00:12:06.750
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.
00:12:18.390
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 contains some security features.
00:12:35.890
This ID card has an issuer from the internet, a subject name, expiration and issued dates, and this pretty sweet official stamp for security.
00:12:54.230
But it's actually up to you to check the card and detect forgeries to ensure that you can actually take this identification.
00:13:01.350
So here's how you do it: First, check if it's from someone recognized as an authority—check the issuer; then, was it intended for you? Check the audience.
00:13:18.690
Has it expired? Check the expiration; is it a forgery? Check the signature to see if you can recreate it based on the values provided.
00:13:27.330
Finally, confirm whether it was generated before or after the time we had to change our secret—like if we accidentally published it on GitHub.
00:13:42.240
If you can check those live questions, you're in pretty good shape, and the good news is you can get a library to handle this for you.
00:13:50.810
Okay, so we’ve learned that JSON Web Tokens are secure messages like Rails signed session cookies.
00:14:04.100
We’ve learned that they contain claims we need to verify, and we’ve discussed the most important claims and what they represent.
00:14:13.150
So let’s talk about what we can do with these tokens. We’ve already mentioned identity tokens, so let's continue from there.
00:14:28.490
One of the problems we had in our Facepage app was the multiple authentication systems that needed to be integrated, so let's see how JSON Web Tokens can help.
00:14:42.580
First, let's add JSON Web Tokens to our comparison table. The Rails sessions are tied to the cookie header, while the API tokens use the authorization header.
00:14:54.880
Our JSON Web Tokens are ready for either header; they don't care. Rails cookies are structured data signed with cryptography, and our JSON Web Tokens share those characteristics.
00:15:07.510
If we use an identity JSON Web Token for login, it might look like this: the browser submits the login, and Rails responds with a JSON Web Token and a cookie.
00:15:24.110
The JWT contains the user ID as a subject. Now, in future requests, the browser just sends it back in the cookie, and that looks pretty familiar, which is a good thing.
00:15:39.690
We actually haven't changed our headers or the relationship between the browser and the server; we're still using cookies.
00:15:51.350
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 sending a string back and forth.
00:16:08.660
But now that string is the JSON Web Token. It has structured data; it has meaning; it’s not random, and the server can do something with it.
00:16:22.960
This is a JSON Web Token solution: one token, two headers, one authentication system.
00:16:35.170
It doesn't matter whether the server finds the token inside a cookie or inside an authorization header; it can still handle that value the same way.
00:16:49.200
Problem number two in our Facepage app: previously, the API had to execute a query on every request just to discover who was making it.
00:17:09.150
Now, our API can verify the JWT with the claims and the cryptography, replacing a network-bound database bottleneck process with a straightforward CPU-bound calculation.
00:17:23.640
This will perform faster, scale better, introduce less variation into response times, and generally just have fewer failure modes.
00:17:38.270
Problem number three: our cookies were implemented for Rails by Rails. Now, don't get me wrong—the default Rails session store is wonderful.
00:17:52.150
It works, it’s hidden, it's secure, it is very well designed, and it does the job it needs to do. However, it’s tightly coupled to Rails.
00:18:06.330
If any single thing doesn't work for you, you have to ask yourself, 'What's next?' JSON Web Token libraries are implemented in at least 20 languages.
00:18:21.120
They're decoupled from cookies and contain claims that you can use to build any kind of distributed architecture, so they're more flexible.
00:18:30.460
They provide a more generalized solution.
00:18:41.420
Problem number four: in a distributed architecture, you might find yourself sharing secrets. This means signing a message with one system to verify it in another.
00:19:02.250
This can involve trusted back channels like copy and paste or configuration management systems. And when the secret exists in multiple places, it creates a bigger attack surface.
00:19:14.700
If one of those locations is compromised, that secret can be used to attack the others. So what do JSON Web Tokens offer? They support asymmetric key algorithms like RSA.
00:19:34.800
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, rehash it later, and verify it.
00:19:49.900
The required setup for RSA is a little bit different. The server signing the token needs a special RSA key, not just a random salt.
00:20:05.800
The server uses the private key to sign the token and can publish the public key on a free and open HTTP endpoint using a specification like JSON Web Keys.
00:20:27.750
When some other server receives the message, it can fetch the public key, use that to verify the token, and then cache it forever.
00:20:37.630
So one HTTP call automatically shares the secret. But it’s not really a secret; it’s the public key.
00:20:50.090
This investment means you don't need to share secrets. It’s a little bit more upfront, but the operational cost is lower.
00:21:05.180
This means there’s no 'copy and paste' between systems. You can just fetch the key over HTTP—there's no need for a highly coordinated 'lockstep' deployment.
00:21:21.890
This also reduces the attack surface: a lost secret can only attack the service that leaked it. This can even prepare you for nifty automatic key rotation.
00:21:42.960
I'm not going to get into that right now, but if you're curious about how that works, feel free to come find me at the conference.
00:21:56.530
Problem number five: you might think you know how password resets work. You generate and send a token; it's a nonce, a random string.
00:22:12.740
You verify it in your controller, then regenerate the token to expire the old one you sent out, making sure that people can't hack the system. When you think about it, this is actually a third authentication system.
00:22:36.730
It works a lot like the opaque API tokens, and again, it’s implemented as a one-off.
00:22:56.800
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.
00:23:10.010
Then, I add a scope claim. I looked around, and this one doesn't seem standard, but I couldn't find a better name, so I'll just call it scope-a.
00:23:22.620
The idea here is that I’ll configure my passwords controller to accept tokens with this scope and configure the rest of my application to reject tokens with this scope or any inappropriate ones.
00:23:38.090
Then, I add an optimistic lock. An optimistic lock is where you maintain some kind of version, and every time a field updates, you increment that version.
00:23:59.750
Anyone who wants to make a change must tell you the current version. This ensures they don't overwrite something that changed without their knowledge.
00:24:11.270
You can achieve this using a timestamp. So, that's what I've done here, maintaining a user's password change timestamp, which is also good for other features.
00:24:25.160
Then, verify the timestamp against its own. If it matches, you’re good to go. This effectively also expires the old reset tokens as soon as the password changes.
00:24:40.650
Once again, we can upgrade opaque tokens into structured signed data.
00:24:50.200
This is one less field in the user's table, one less index for your queries, but even better, we’ve absorbed a third authentication system by teaching our JSON Web Token back-end about scopes.
00:25:02.170
We’ve also educated our passwords controller about optimistic locks.
00:25:14.200
Problem number six: suppose you're sending emails with survey links or some kind of strong call-to-action where you need to know who's clicking.
00:25:26.270
If you don't help them at all, they'll hit a login wall. And if they’re on their phones, they might not want to type it in, so they could just come back later—or not!
00:25:40.420
This would hurt your conversion rate. So, you might implement random strings as opaque tokens, connecting them back to the user, similar to the API system. I mean, this is sounding pretty familiar, right?
00:25:59.610
We can 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, yes, that’s exactly what I'm suggesting.
00:26:16.180
Basically, that's what the randomly generated one-off strings are doing—they're giving a login session via email. But this one is built into our authentication system.
00:26:31.300
It’s not a one-off implementation that you’re going to forget about.
00:26:42.570
Problem number seven: your application is already doing a lot of stuff. You're including many common standard authentication features as if they're unique.
00:26:56.850
These features run from the same database, and they can be affected by every deployment, every upgrade. All this complexity is in one spot, making it harder to audit your attack surface.
00:27:16.510
Let's not forget the always-present user-god model. JSON Web Tokens can help with this because they were born ready for this.
00:27:33.470
This is why the issuer and audience claims exist: they can be different things. The issuer can take responsibility for the account.
00:27:45.700
This account could contain details like the username, the password, the last time the password was changed, and the number of failed login attempts.
00:27:58.050
This shifts responsibility for account tracking away from your application and allows it to maintain a simpler user model. It merely relates the user to an account.
00:28:14.890
This is actually what I learned from working on Keratin Auth, which is what you would get if Devise was rebuilt as a standalone authentication service.
00:28:30.200
It removes complexity from your application and relies heavily on JSON Web Tokens. Every trick I’ve mentioned here, and then some. The core technology is stable.
00:28:44.080
I've got ideas for some advanced features, so if anyone's interested, I’d love to chat about them.
00:29:01.600
In conclusion, here are the takeaways from this presentation: 1. You can use JSON Web Tokens right now. It doesn't matter if you have a monolith or services. You may recognize one or more of these problems.
00:29:15.510
2. JSON Web Tokens have a low learning curve and a high skill ceiling. There's much more you can do with them as you gain confidence, so start somewhere.
00:29:31.480
If you leave this conference with a head full of ideas for things you wish you could use at your day job, perhaps JSON Web Tokens are among them. Pick something, learn it by doing it, and make your knowledge real.
00:29:49.570
All right! Do we have time for questions?
00:30:03.640
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 ensure they don't return?
00:30:13.220
You can create a blacklist and maintain a temporary cache of invalidated tokens. If you choose, for critical actions, you can implement revalidation.
00:30:25.520
The trade-off is real. If you keep authentication in a token that lives for 30 minutes, I recommend that those tokens have a short lifespan and that you regenerate them frequently.
00:30:36.800
This way, the time window for such a problem is small.
00:30:48.090
So again, the comment is that checking this blacklist takes time. I recommend determining which endpoints are critical and revalidating only for those.
00:30:59.320
There are probably many endpoints where it's acceptable for the user to continue reading data for a few more minutes.
00:31:06.520
Alternatively, you could protect that data and choose to revalidate where necessary.
00:31:19.640
You can try JSON Web Tokens in an existing app by finding any kind of opaque token and exploring how you can replace it.
00:31:32.730
If you aim to try an authentication server, it helps to have an app with some kind of login scheme where you can swap out where the form submits.
00:31:47.540
The form might submit to your back-end, while another form submits to a different back-end. Those are a couple of ideas.
00:32:02.490
Is there a drop-in replacement?
00:32:10.570
There’s a library called Knock, and Knock works like Devise but with JWT. It’s built into the monolith.
00:32:30.570
However, an authentication server will run as a separate deployment, while Knock integrates JWT as part of the same model.
00:32:44.800
It's refreshing because it uses more tokens. The way I've seen it, you maintain what's called an access token and a refresh token.
00:33:03.890
The access token is what you send to the API, while the refresh token is more secure because it's not used very much. Its sole purpose is to fetch new access tokens.
00:33:21.090
For instance, imagine your access token is valid for an hour, and you choose to regenerate it every 30 minutes.
00:33:39.680
You can build revocation into that refresh system. The refresh could query Redis or MySQL to check if it's still valid to generate new access tokens.
00:33:58.570
This dual token system allows for different security properties while maintaining a lightweight protocol for chatty requests.
00:34:09.160
The idea is that if you attempt to use a token that's expired, you realize you need a new one.
00:34:23.770
The recommendation is to aggressively refresh before that expiration occurs, creating a smoother user experience.
00:34:37.280
If you refresh efficiently enough, you may not need to deal with that last-minute 'let's get a new one' situation.
00:34:51.580
In some dire situations, which we all hope to avoid, there's truly no going back.
00:35:01.940
You generate a new key and switch to using it. Then you would implement an epoch to state that any token generated before this point in time was using an old key and is no longer trusted.
00:35:20.290
For example, if the issue date was set before last Friday, when the compromise occurred, just discard it. This means users would be logged out.
00:35:32.630
The comment was that you'd need multiple keys and an epoch for each key. JSON Web Tokens have a claim called key ID.
00:35:48.830
You can embed within each JWT a signature reflecting the specific key used, allowing you to monitor which were used and if still reliable.
00:36:03.420
For basic login sessions, it's straightforward—the user ID. If you choose to store this in a cookie, you'll need to manage CSRF.
00:36:17.930
You might also include the CSRF token within the JWT because you can add more information freely.
00:36:27.960
I'm actually not a big fan of storing extensive data in sessions, so I recommend keeping your JWT payload concise. Yes, it may be a bit longer than a Rails equivalent cookie, but it's a trade-off for having more information accessible.
00:36:42.450
Some advocates promote saving more user information in sessions, such as admin roles, permissions, and basic contact info. It could save on queries, but take caution—think about how stale that information might become.