Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine
This tutorial has a new version, check it out!

JSON Web Tokens (are awesome)

Keep on Learning!

If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.

Start your All-Access Pass
Buy just this tutorial for $10.00

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

How does authentication normally work on the web? Usually, after we send our username and password, a cookie is returned to us. Then, on every request after, we send that cookie back to the server: the cookie is delicious, and identifies who we are, it's our key to the app. The server eats that cookie, I mean reads that cookie, and looks it up in some database to figure out who we are.

How all (most) API Authentication Works

Guess what? An API isn't much different. One way or another, an API client will obtain a unique token, which - like the cookie - acts as their key to the API. On every request, the client will send this token and the server will use that token to figure out who the client is and what they're allowed to do.

How does it do that? Typically, the server will have a database of tokens. If I send the token I<3cookies, it can query to see if that's a valid token and to find out what information might be attached to it - like my user id, or even a list of permissions or scopes.

By the way, some of you might be wondering how OAuth fits into all of this. Well, OAuth is basically just a pattern for how your API client gets the token in the first place. And it's not the only method for obtaining tokens - we'll use a simpler method. If OAuth still befuddles you, watch our OAuth2 Tutorial. I'll mention OAuth a few more times, but mostly - stop thinking about it!

Anyways, that's token authentication in a nut shell: you pass around a secret token string instead of your username and password.

A Better way? JSON Web Tokens

But what if we could create a simpler system? What if the API client could simply send us their user ID - like 123 - on each request, instead of a token. Well, that would be awesome! Our application could just read that number, instead of needing to keep a big database of tokens and what they mean.

Alas, we can't do that. Because then anyone could send any user ID and easily authenticate as other users in the system. Right? Actually, no! We can do this.

In your browser, open jwt.io: the main website for JSON web tokens. These are the key to my dream. Basically, a JSON web token is nothing more than a big JSON string that contains whatever data you want to put into it - like a user's id or their favorite color. But then, the JSON is cryptographically signed and encoded to create a new string that doesn't look like JSON at all. This is what a JSON web token actually looks like.

But wait! JSON web tokens are encoded, but anyone can read them: they're easily decoded. This means their information is not private: you would never put something secret inside a JSON web token, like a credit card number - because - it turns out - anyone can read what's inside a JSON web token.

But here's the key: nobody can modify a JSON web token without us knowing. So if I give you a JSON web token that says your user ID is 123, someone else could steal this token and use it to authenticate as you. But, they cannot change the user ID to something else. If they do, we'll know the token has been tampered with.

That's it! JSON web tokens allow us to create tokens that actually contain information, instead of using random strings that require us to store and lookup the meaning of those strings on the server. It makes life simpler.

Oh, and by the way - once you eventually deploy your API, make sure it only works over SSL. No matter how you do authentication, tokens can be stolen. So, use HTTPS!

Now that we know why JSON web tokens - or JWT - rock my world, let's use them!

Leave a comment!

16
Login or Register to join the conversation
Abelardo Avatar
Abelardo Avatar Abelardo | posted 7 months ago

I am lack of knowledge about jwt world.

When a client and a server interchange jwt tokens during authentication/authorization proccesses, what is the next step for using it? So, does the client need to send it each time it makes a get, put, post, etc. request or it only serves to auth processes?
If so, the client has to store this data: via cookie or by security reasons is better it is not stored??
Am I wrong? Best regards.

Reply

Hey @AbelardoLG!

Excellent question(s)!

> So, does the client need to send it each time it makes a get, put, post, etc. request or it only serves to auth processes

Yes. If you're using a token authentication solution, then in JavaScript, on every API call, you would include it.

> If so, the client has to store this data: via cookie or by security reasons is better it is not stored??

This is one big complication or downside of trying to manage tokens in JavaScript (vs using cookie-based session authentication... but session-based cookie authentication is NOT an option if your frontend JS and backend API live on different domains). I'm not an expert on this, and I know that it needs to be done thoughtfully. I'd check out Auth0 on this: they are a pro source for info: https://auth0.com/docs/secu...

My preference, in general, is to avoid messing with API tokens in JavaScript and instead use cookie-based session authentication. However, this requires that you have a backend that lives on the same domain as your frontend. But, that's *really* the easiest path. Even big sites like GitHub - who have a fully-featured API - do not actually use their API from their frontend. If you look carefully, GitHub's JavaScript doesn't make Ajax calls to their API: they make Ajax calls to github.com itself... which then returns data (behind the scenes GitHub.com itself might/could make API requests back to api.github.com if it wanted to... and it would be able to do that safely, as it could store any access tokens in the backend).

I hope this helps a bit :).

Cheers!

1 Reply
Abelardo Avatar

Btw, I am going to navigate through your website by looking for a course about how to handle session cookies with Symfony.
Thanks for your nice website! :)

Reply

Hey Abelardo,

We glad to hear our website is useful for you! Make sure to check out advanced search on the website, it searches in video, comments and even in code blocks we have below videos! It should help a lot with finding information :)

Cheers!

1 Reply
Abelardo Avatar

Then no problem in my case!: inside my twig templates my JS scripts live. Here I could add my Ajax calls to my Symfony API.
But...then my jwt token would be visible.mmmm....🤔 A big problem, right? My JWT Token could be used by using bad techniques to hack my website. 🤔

Imagine this scenery:
" < a href = "{{ path('aRoute', { myJWTToken: jwtToken } ) }}"> Edit this user < / a > "
At this place, my jwtToken would be visible by inspecting the source code.

Am I right or again I am wrong to understand how to use the jwtToken inside my Twig templates (no JS here!)?

Thanks and best regards, Ryan!

Reply

Hey Abelardo L.!

Yea, this gets tricky. So first, don't use JWT (or any API tokens in your JavaScript) if you can avoid it. By "cookie-based session", I am referring to (A) creating a normal log in form and (B) POSTing to that login form via Ajax. That's it :). The login form AJAX post response will return a session cookie... and all future Ajax calls will pass that cookie and be authenticated.

If you ARE using API tokens in JavaScript, I'm not an expert here. But certainly, your example of embedding it into HTML would be a problem. I think the idea is generally to store the token in ONE place and for that to be "in memory" (e.g. not somewhere like local storage) and NOT global. So even doing window.API_TOKEN = '' makes it possible for some 3rd party JS on your site to read that. In other words, it's tricky. I look to Auth0 here for best practices on how to store the tokens.

But, more generally, this example looks a bit odd to me:


<a href = "{{ path('aRoute', { myJWTToken: jwtToken } ) }}"> Edit this user </a>

This tells me that you have a Symfony backend that is rendering HTML. In that case, there's no reason to use API tokens. I'd log into "my backend" use a normal login form (you can submit it via Ajax if you want it to look fancier). If this jwtToken is... actually so that you can reference some OTHER API (i.e. you have (i) a frontend, (ii) a Symfony backend and (iii) a separate API that you talk to), then you should store the jwtToken on the backend somewhere (e.g. even in the session) and then use that in your Symfony code to make API calls.

Let me know if this is helping to clarify... or not :).

Cheers!

Reply

I'm thinking of implement a system where the user authenticates with JWT, and then, using the jwt token, the user can request an access_token and that will decide what the user can and cannot do (To keep things small, the symfony app would act as both the authorization server and resource server) . I wonder if there are bundles that already offer this functionality, I've reasearched into a few and I'm planning on finishing some courses on this page about that matter, but before I dig in too deep I need to check I'm not chasing a wilde goose here.

Reply

Hey ponder!

Sorry for the slow reply - SymfonyCon was last week and kept us busy! First, what's your motivation for using the JWT for authentication and then a separate access_token after? I'm just making sure I understand what you're trying to do. And second, there is probably not a bundle for what you're thinking of - there is of course LexikJWTAuthenticationBundle for using JWT in general, but for a more specific implementation, you'll probably need to add it yourself. So, you are not missing anything!

Cheers!

Reply
Lijana Z. Avatar
Lijana Z. Avatar Lijana Z. | posted 4 years ago

I did not get. So the benefit is that we send user id, and we get jwon web token which contains various information? But we first maybe have to still send username and password, so that anyone would not be able to send the user id and get information about the user?

If that is how I wrote then: why we cannot send credit card information if the user is authenticated and we send that token over SSL? Nobody besides authenticated user will be able to see the credit card information.

Reply

Hey Lijana Z. !

Really good question. The WHOLE point of "API tokens" is this: to have some "string" that allows access that is different than the user's password. Let me say it a different way. Yes, if you're using SSL, there is no security risk to sending your email/password, credit card information, API token or anything else to the server. So then, why do we have API tokens at all? Why not just have the user *always* send their email/password to the API endpoints?

Actually, this *is* a valid way to do it. And, like we just talked about, over https, this doesn't really have any security implications.
The *real* reason for API tokens is that you can create an API token and safely give it to someone else. Then, if you decide that this "other person/app" should not have access to your account anymore, you could (in theory, it depends on how you build your system) invalidate/delete that API token. Heck, you could give out 10 different API tokens to 10 different users. And if you want to deny access to just one of them, you only need to invalidate/delete that API token for 1 app. The other 9 will work, as will your password. Additionally, in a more complex system, you can make some tokens have different permissions than others.

So, if api tokens did not exist, we would be forced to give other apps our password. And then, if we changed our password (because we wanted to deny access to 1 app that we shared it with), we would invalidate access for all of those apps. This is how, for example, OAuth works with Facebook. If you clicked "Login with Facebook" on SymfonyCasts, you are basically creating & giving SymfonyCasts an API token that gives some access to your Facebook account. SymfonyCasts then sends that token on API requests to Facebook to perform certain actions "as you" (well, we don't do that - we just fetch your email for login purposes, but you get the idea). If you wanted to "invalidate" our access, you could go to Facebook and "revoke our access", which basically makes our API token invalid.

Phew! The big question with all this API token stuff is: how do you want your users to be able to create API tokens? In this simple app, we created an API endpoint where you can create them. Then, in theory, you could safely give those to someone else. We could also implement an OAuth server (then we would operate a big like Facebook) OR we could create a simple profile section where each user can manually create access tokens (you can do this on GitHub). We actually have an entire short video on this exact topic: https://symfonycasts.com/sc...

Let me know if that helps!

Cheers!

Reply
Lijana Z. Avatar

Thats a long answer. I think I did not understand most of the things, I need to reread it slower but currently have other stuff to watch. I mark it to come back later. But one thing instatly raises question to me:

"So, if api tokens did not exist, we would be forced to give other apps our password. And then, if we changed our password (because we wanted to deny access to 1 app that we shared it with), we would invalidate access for all of those apps."

You mean 1 password for every user? I think every user would have their own pasword and so we just deny access to one user. But since I did not take time to think much, that might be a reason why I did not understand.

Reply

Hey Lijana Z.!

> So, if api tokens did not exist, we would be forced to give other apps our password. And then, if we changed our password (because we wanted to deny access to 1 app that we shared it with), we would invalidate access for all of those apps.

Ah yes, I DO mean that every user would have their own password. Think about Facebook as a good example: imagine I have an account on Facebook (and I of course have my own, unique password). Now, I want to allow some other site - e.g. SymfonyCasts - to access some information on my Facebook account via their API. If API tokens did not exist, then I would need to give SymfonyCasts my Facebook password... so that SymfonyCasts could use it in API calls to Facebook. Sure, there might be the same fancy OAuth flow to "pass" my password to SymfonyCasts. But, the important thing is that, ultimately, SymfonyCasts would have my password. Now, imagine I also want to allow some other site - e.g. GitHub - to be able to access my Facebook account via the API. Now I would *also* need to give GitHub my Facebook password.

This situations has 2 major issues:
1) Because SymfonyCasts and GitHub have my password, they can actually do ANYTHING with my Facebook account, including post things to my wall. One of the advantages of API tokens is that (if you need to) you can make an API token only have *some* permissions for an account, instead of ALL permissions.
2) Imagine that we no longer trust GitHub and we want to "revoke" access - we don't want GitHub to be able to access our Facebook account anymore. The only way to do that is to change my password on Facebook. But, when I do that, I've also (accidentally) revoked access to SymfonyCasts.

I hope that makes sense! The argument for API tokens is pretty clear. But also, you don't *really* need them unless you want 3rd parties to access your API. If you're building an API ONLY so that your OWN JavaScript can access it, using session cookies is a simpler approach.

Cheers!

Reply
Default user avatar

Can you point out, when the private key and when the public key is used? I think it is different from an asymmetric key encryption scheme?

Reply

Hey einue!

Ah, very interesting question :). I'll explain a bit using the new (version 2) of the https://github.com/lexik/Le..... though the details will essentially be the same for version 1.4.

The most important thing to know is that the bundle relies on a *different* library to actually do the key stuff. It either uses https://github.com/namshi/jose or https://github.com/lcobucci... based on your configuration. So let's talk about the first. If you look at their docs, they explain it fairly well: https://github.com/namshi/j.... Basically, the private key is used to sign the key, and then the public key is used to verify it.

This *is* different than asymmetric key encryption. But... it's kind of the same idea in a different direction. I may be off on some finer details (security is always tricky), but basically, if you have a public and private key, you can use it in 2 different ways:

A) Encryption: Encrypt data with a public key, and decrypt with a private key (like how SSL works)
B) Signing: Sign data with a private key and "verify" its authenticity with a public key (JWT)

I hope that helps!

Cheers!

Reply
Default user avatar

The typo at 3:54 "authenticting" triggered me a bit, just a little bit though ;)

Reply

Just making sure you're aware ;). Thanks for pointing that out - we'll get that fixed and re-export.

Cheers!

Reply
Cat in space

"Houston: no signs of life"
Start the conversation!

This tutorial uses an older version of Symfony. The concepts of API tokens & JWT are still valid, but integration in newer Symfony versions may be different.

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": ">=5.5.9",
        "symfony/symfony": "3.0.*", // v3.0.3
        "doctrine/orm": "^2.5", // v2.5.4
        "doctrine/doctrine-bundle": "^1.6", // 1.6.2
        "doctrine/doctrine-cache-bundle": "^1.2", // 1.3.0
        "symfony/swiftmailer-bundle": "^2.3", // v2.3.11
        "symfony/monolog-bundle": "^2.8", // v2.10.0
        "sensio/distribution-bundle": "^5.0", // v5.0.4
        "sensio/framework-extra-bundle": "^3.0.2", // v3.0.14
        "incenteev/composer-parameter-handler": "~2.0", // v2.1.2
        "jms/serializer-bundle": "^1.1.0", // 1.1.0
        "white-october/pagerfanta-bundle": "^1.0", // v1.0.5
        "lexik/jwt-authentication-bundle": "^1.4" // v1.4.3
    },
    "require-dev": {
        "sensio/generator-bundle": "^3.0", // v3.0.6
        "symfony/phpunit-bridge": "^3.0", // v3.0.3
        "behat/behat": "~3.1@dev", // dev-master
        "behat/mink-extension": "~2.2.0", // v2.2
        "behat/mink-goutte-driver": "~1.2.0", // v1.2.1
        "behat/mink-selenium2-driver": "~1.3.0", // v1.3.1
        "phpunit/phpunit": "~4.6.0", // 4.6.10
        "doctrine/doctrine-fixtures-bundle": "^2.3" // 2.3.0
    }
}