JSON Web Token is a simple way to send information in the clear (usually in a URL) whose contents can be verified to be trusted. When might you use something like this?
- In a Single Sign-On scenario where you want a separate authentication server that can then send user information in a trusted way. I wrote omniauth-jwt recently for just this purpose
- When generating "one-click" email action links (as I talked about once before on this blog, but this is a better method!)
- Any time you have the need to communicate a small payload of information between two sources via an unsecured transport
Before we get started, it's important to note that JWT does not encrypt the payload, it only signs it. You should not send any secret information using JWT, rather you should send information that is not secret but needs to be verified. For instance, sending a signed user id to indicate the user that should be logged in would work great! Sending a user's password would be very, very bad.
JWT works by simply encoding a string made up of a small JSON object and hashing it using a secret shared between the two parties. The algorithm is configurable, but is usually HMAC SHA-256. So how do we do this in practice? We'll take a look at a simple example of an email action link. Let's say we have a new social network and we're sending an email for someone to confirm a friend request. The information we want to send might look something like this:
{ "user_id":12345, "friend_id":23456 }
Of course, we can't just send a link that would, without any verification, friend one user ID to another. So we'll need to use JWT to sign it! Our example here will be in Ruby, and you'll need the jwt
gem in your Gemfile.
require 'jwt' EMAIL_SECRET = 'im-a-teapot' payload = JWT.encode({ user_id: 12345, friend_id: 23456 }, EMAIL_SECRET) link = "http://myapp.com/emails/friend?jwt=#{payload}"
You would then send the above link in the email and let the user know that if they click it they'll accept the friend request of the user represented by friend_id
. The user then clicks the button. So how do we verify this when the link is clicked?
get '/emails/friend' do payload = JWT.decode(params[:jwt], EMAIL_SECRET) User.find(payload["user_id"]).add_friend!(payload["friend_id"]) "You accepted the friend request!" end
That's all there is to it! Now the users are friends and there was no complicated login process needed! Of course, there are security concerns with this approach. As it stands, there's nothing stopping someone from "replaying" this token at a later date. If the users stop being friends but the same URL is visited a year later, the link will still work. JWT comes with a few different recommended ways to mitigate this:
- You can include an
iat
claim in your payload that is a UNIX timestamp of when the token was issued. The decoder should then check that this timestamp is within a certain valid window or otherwise reject it. - You can include an
exp
claim in your payload that is a UNIX timestamp indicating when the token will expire. The decoding end should check that the current time is before the expiration and otherwise reject the token. - You can assign a unique value to the
jti
claim. This way, the decoding end can check to make sure that the token has never been consumed before, making sure that it is one-use.
There are a few other registered claim names that might come in handy depending on your usage.
That's really all there is to it. There are JWT libraries available for most languages, and they're all very simple to use. JWT is a fantastic and simple way to communicate trusted information across untrusted channels. Hope you find a good use for it soon!