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

LexikJWTAuthenticationBundle

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

Google for LexikJWTAuthenticationBundle. This bundle is going to make creating and validating JSON web tokens as much fun as eating ice cream. Click to read the documentation. And now, you guys know the drill. Copy the library name from the composer require line and run:

composer require lexik/jwt-authentication-bundle

Tip

The latest version of lexik/jwt-authentication-bundle requires Symfony 3.4 or higher. Run composer require 'lexik/jwt-authentication-bundle:v2.4' to install this bundle for older versions of Symfony, like the one that is used in this screencast. Or, upgrade your project's dependencies.

While we're waiting for Jordi, I mean Composer to download that for us, let's keep busy. Copy the new bundle line and put that into AppKernel:

... lines 1 - 5
class AppKernel extends Kernel
{
public function registerBundles()
{
$bundles = array(
... lines 11 - 20
new Lexik\Bundle\JWTAuthenticationBundle\LexikJWTAuthenticationBundle(),
);
... lines 23 - 32
}
... lines 34 - 51
}

Great!

Generating the Public and Private Key

Our first goal is to write some code that can take an array of information - like a user's username - and turn that into a JSON web token. This bundle gives us a really handy service to do that.

But before we can use it, we need to generate a public and private key. The private, or secret key, will be used to sign the JSON web tokens. And no matter what the FBI says, this must stay private: if someone else gets it, they'll be able to create new JSON web tokens with whatever information they want - like with someone else's username to gain access to their account.

Copy the first line, head to the terminal and wait for Composer to finish all its thinking. Come on Jordi! Don't worry about the error: this bundle has some required configuration that we're about to provide.

First, make a new directory to hold the keys:

mkdir var/jwt

Next, copy the second line to create a private key, but change its path to the var/jwt directory:

openssl genrsa -out var/jwt/private.pem -aes256 4096

This asks you for a password - give it one! It adds another layer of security in case somebody gets your private key. I'll use happyapi. Perfect!

Last step: copy the final line and remove app at the beginning and the end to point to the var/jwt directory:

openssl rsa -pubout -in var/jwt/private.pem -out var/jwt/public.pem

Type in the password you just set. This creates a public key. It'll be used to verify that a JWT hasn't been tampered with. It's not private, but you probably won't need to share it, unless someone else - or some other app - needs to also verify that a JWT we created is valid.

We now have a private.pem and a public.pem. You probably will not want to commit these to your repository: the private key needs to stay secret. But there's good news! You can create a key pair to use locally and then generate a totally different key pair on production when you deploy. They don't need to be the same. Just don't change the keys on production: that will invalidate any existing JSON web tokens that your clients have.

Configuring the Bundle

Ok, last step: tell the bundle about our keys. Copy the configuration from the docs and open up app/config/config.yml. Paste this at the bottom:

... lines 1 - 72
lexik_jwt_authentication:
private_key_path: %kernel.root_dir%/../var/jwt/private.pem
public_key_path: %kernel.root_dir%/../var/jwt/public.pem
pass_phrase: %jwt_key_pass_phrase%
token_ttl: 3600

Instead of using all these fancy parameters, it's fine to set the path directly: private_key_path: %kernel.root_dir% - that's the app/ directory - /../var/jwt/private.pem. Do the same for the public key, with public.pem. Set the token_ttl to whatever you want: I'll use 3600: this means every token will be valid for only 1 hour.

Finally, open parameters.yml and add the jwt_key_pass_phrase, which for me is happyapi. Don't forget to add an empty setting also in parameters.yml.dist for future developers:

... line 1
parameters:
... lines 3 - 20
jwt_key_pass_phrase: ~

Phew! That's it! We had to generate a public and private key, but now, life is going to be sweet. Run:

bin/console debug:container jwt

Select lexik_jwt_authentication.jwt_encoder. This is our new best friend for generating JSON web tokens.

Leave a comment!

22
Login or Register to join the conversation
Default user avatar
Default user avatar argy_13 | posted 5 years ago

Hi guys,

i tried to install with the command
composer require "lexik/jwt-authentication-bundle"

and it failed totally with some notes like:
"Your requirements could not be resolved to an installable set of packages."
"symfony/security-bundle v3.4.0 conflicts with symfony/symfony[v3.0.3]."
and at the end
"Installation failed, reverting ./composer.json to its original content."

So i wrote the sentence below and i didn t have no one problem with the instalation
composer require "lexik/jwt-authentication-bundle" ^2.0

1 Reply

Hey argy_13 ,

Hm, "composer require lexik/jwt-authentication-bundle" and "composer require 'lexik/jwt-authentication-bundle:^2.0'" are the same actually and should install the latest v2.5.3 version, but looks like Composer works in a bit different strategies. When you do not specify a certain version - it looks a compatible release in 2.5 branch only, but if you specify a version manually, Composer look for a compatible version in a range from the specified version to the latest version, that's why the 2nd command works.

In shorts, the 2.5 branch of lexik/jwt-authentication-bundle was bumped to support Symfony 3.4+ only, but we we're on 3.0 in this course. Btw, you can run: "composer why-not 'lexik/jwt-authentication-bundle:^2.5'" to get more info from Composer why not. And thanks for sharing this problem with others!

Cheers!

1 Reply
Jelle S. Avatar
Jelle S. Avatar Jelle S. | posted 3 years ago

Hi guys, can we use this tutorial for Symfony 4?
Also, can't we already protect url's from unwanted visitors by adding the security annotations, isn't that enough if you only want to use Symfony?
Keep being awesome!

Reply

Yo Jelle S.!

Hi guys

Hey! o/ 😀

can we use this tutorial for Symfony 4?

Yes and no. You'll definitely need to adapt some big things, as this tutorial is getting pretty old. It's still a great tutorial to learn some general things from, but it's starting to show its age. Our much newer tutorial about API Platform is another great option and can get you set up faster. Though, this tutorial teaches you many "lower-level" things. So it depends if you are here to learn some deep stuff or just get some work done quickly :).

Also, can't we already protect url's from unwanted visitors by adding the security annotations, isn't that enough if you only want to use Symfony?

Are you talking about things like @Security("is_granted('ROLE_USER')") (or the simpler, and equivalent @IsGranted("ROLE_USER"))? If so... yes... but I'm not sure if I'm answering the question correctly. We do use these annotations in this tutorial and they're great. And you can use them whether you're building an API or a traditional web app. Those annotations and access_control in security.yaml and $this->denyAccessUnlessGranted() are all about "authorization": denying the current user access to something based on some logic. How a user authenticates - a login form, JWT token, something else crazy - is a totally separate topic. You can have a login form and use the security annotations just fine. Or if you need some fancy JWT thing, you can also use the security annotations. That system doesn't actually know or care how the user logged in: it's simply checking that whoever is logged in has a role or not.

Let me know if that helps! I have a tendency to sometimes completely answer the wrong question ;).

Cheers and happy new year!

Reply
Default user avatar
Default user avatar Wazir Khan | posted 4 years ago

Hi guys,

Struggling with generating public key from private key in windows 10, couldn't succeed, googled a lot as well. First I was stuck with private key then I used -passout pass:Password option and it worked, now public key command is not working. If anyone one has faced the same issue and resolved it please let me know your solution.

Thank you in advance.
Wazir

Reply
Default user avatar

Resolved this issue, if anyone else facing the same issue, please install openssl again, download it from here.

Reply

Hey Wazir,

I'm glad you got it working! And thank you for sharing your solution with others.

Cheers!

Reply
Shaun T. Avatar
Shaun T. Avatar Shaun T. | posted 4 years ago | edited

I am trying to setup Leixk JWT Authentication Bundle, and I have a strange issue with using environment variables.

config/packages/lexik_jwt_authentication.yaml:


// Works
lexik_jwt_authentication:
    public_key:   '%env(resolve:JWT_PUBLIC_KEY)%'
    secret_key:   '%env(resolve:JWT_SECRET_KEY)%'
    pass_phrase:  'ilikedogs'

// Doesn't work
lexik_jwt_authentication:

public_key:   '%env(resolve:JWT_PUBLIC_KEY)%'
secret_key:   '%env(resolve:JWT_SECRET_KEY)%'
pass_phrase:  '%env(resolve:JWT_PASSPHRASE)%'


This is my .env file, am I doing something wrong here?

# This file is a "template" of which env vars need to be defined for your application
# Copy this file to .env file for development, create environment variables when deploying to production
# https://symfony.com/doc/current/best_practices/configuration.html#infrastructure-related-configuration

###> symfony/framework-bundle ###
APP_ENV=dev
APP_SECRET=2a9b9734ac76f90a8d60b0756d62c868
#TRUSTED_PROXIES=127.0.0.1,127.0.0.2
#TRUSTED_HOSTS=localhost,example.com
###< symfony/framework-bundle ###

###> doctrine/doctrine-bundle ###
# Format described at http://docs.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/configuration.html#connecting-using-a-url
# For an SQLite database, use: "sqlite:///%kernel.project_dir%/var/data.db"
# Configure your db driver and server_version in config/packages/doctrine.yaml
DATABASE_URL=mysql://homestead:secret@127.0.0.1:3306/homestead
###< doctrine/doctrine-bundle ###

###> symfony/swiftmailer-bundle ###
# For Gmail as a transport, use: "gmail://username:password@localhost"
# For a generic SMTP server, use: "smtp://localhost:25?encryption=&auth_mode="
# Delivery is disabled by default via "null://localhost"
MAILER_URL=null://localhost
###< symfony/swiftmailer-bundle ###

###> lexik/jwt-authentication-bundle ###
JWT_SECRET_KEY=%kernel.project_dir%/config/jwt/private.pem
JWT_PUBLIC_KEY=%kernel.project_dir%/config/jwt/public.pem
JWT_PASSPHRASE=ilikedogs
###< lexik/jwt-authentication-bundle ###


Reply

Hey Shaun T.

That's weird, try removing the "resolve" part for "pass_phrase" parameter
What error message did you get?

Cheers!

Reply
Shaun T. Avatar
Shaun T. Avatar Shaun T. | MolloKhan | posted 4 years ago | edited

Hey MolloKhan

Thanks for your reply, I tried removing resolve but that din't make any difference. Here is the error message I get:

[2018-09-05 22:30:09] request.CRITICAL: Uncaught PHP Exception Lexik\Bundle\JWTAuthenticationBundle\Exception\JWTEncodeFailureException: "Unable to create a signed JWT from the given configuration." at /Users/shaunthornburgh/Documents/Development/homemates/vendor/lexik/jwt-authentication-bundle/Encoder/LcobucciJWTEncoder.php line 41 {"exception":"[object] (Lexik\\Bundle\\JWTAuthenticationBundle\\Exception\\JWTEncodeFailureException(code: 0): Unable to create a signed JWT from the given configuration. at /Users/shaunthornburgh/Documents/Development/homemates/vendor/lexik/jwt-authentication-bundle/Encoder/LcobucciJWTEncoder.php:41)"} []

Reply
Diego-B Avatar
Diego-B Avatar Diego-B | Shaun T. | posted 4 years ago | edited

Hmm, and you say that without using a "env" variable it works?

Try this pass_phrase: '%env(string:JWT_PASSPHRASE)%'

Reply
Shaun T. Avatar

Thanks, but that generates the same error message...

Reply

Hey Shaun T.!

Oh boy, you've got a tricky one! First, as you know, the bundle is intended to be used, exactly as you're using it. So, there "shouldn't" (but quotes around that) be any surprises. So... this is surprising ;).

There are a few ways we can start debugging this:

1) Run php bin/console about. Near the bottom, you'll see the current environment variables that are set. My guess is that JWT_PASSPHRASE will be set to your password, but, let's look at this to be sure :).

2) You'll need to do a little digging ;). I'm not 100% sure if this is the right place, but I would try to do a var_dump($this->keyLoader->getPassphrase());die; right before this line: https://github.com/lexik/LexikJWTAuthenticationBundle/blob/3c83eff7a87dcf983fca0a98334946facc0b7169/Services/JWSProvider/LcobucciJWSProvider.php#L147-L150 - I'm curious to see if this is the correct passphrase, or something else. If this die statement isn't hit, let me know.

Mostly, I just can't see why this issue would happen. So... that's why we're debugging :).

Cheers!

Reply
Shaun T. Avatar
Shaun T. Avatar Shaun T. | weaverryan | posted 4 years ago | edited

Thanks for your advice weaverryan

Ok I noticed something weird, if I run bin/console about with the passphrase set manually in config/packages/lexik_jwt_authentication.yaml, it outputs:
string(9) "ilikedogs"

However if I set it to '%env(resolve:JWT_PASSPHRASE)%', it outputs:
string(32) "d9f081115756b52521cb7a1e775d05c3"

Reply

Hey Shaun T.!

Wow! That IS weird! We have a mystery :D. But, I might have an idea. Here is a key phrase from the DotEnv component docs:

Symfony Dotenv never overwrites existing environment variables

I wonder if, somehow, you've setup a JWT_PASSPHRASE environment variable somewhere else. And so, when your .env file is being loaded, it does NOT override this environment variable. You could debug this by opening public/index.php and putting this line right on top:


var_dump(getenv('JWT_PASSPHRASE'));die;

Does this print something?

Cheers!

Reply
Shaun T. Avatar

Hey Ryan,

I got to the bottom of this. JWT_PASSPHRASE was being set separately in phpunit.xml.dist. I updated the value, and everything is working :)

1 Reply
Default user avatar

Hello !

Thank you very much for this lesson. And for the whole website too, it's fantastic.

I'm just having a problem on this particular JWT authentification. I have an API that has been developped in Symfony 3.3 that worked well on the internet. But now that I'm trying to run it locally (to make changes), I don't manage to set JWT authentification properly :/

What I have done so far :
- retrieved public and private keys and put them into the var/jwt folder
- set right passphrase in the parameters.yml file

The token are created and sent to the client. But when the client want to use the token to authenticate himself, it says "Invalid JWT Token". How is it possible ? Since the creation process has worked ...

Reply

Hey @Jazz

Thanks for the compliment :)
First let's double check that you are actually passing the right values to your API, dump your credentials just before using them for authentication. If that's not the case, then, let's keep debugging until find the error!

Cheers!

Reply
Ingvald O. Avatar
Ingvald O. Avatar Ingvald O. | posted 5 years ago

Hello,

Is it possible to use this bundle without a pair login/password ? I would like in my application give the possibility to login with the pair or just with the email. What do you think ?

Thanks

Reply

Hey Ingvald O.!

Hmm. Can you tell me a little bit more about what you want to do? When you talk about the "pair login/password", are you password that's used to create the public & private keys? Do you want to avoid using the public/private keys?

Let me know - I'm just not sure yet what your idea is ;).

Cheers!

Reply
Ingvald O. Avatar

Sorry, Bad question!
No i dont speak about the pair public/private Key, i would like to register my user by an Android app just with their email and i want to know if it is possible or notre with lexikJwt?

Reply

Hey Ingvald O.!

Ah! Yep! this is totally possible. Actually, the LexikJWT library has nothing to do with whether or not you use a username & password. Basically, the "flow" would look like this:

A) You create an endpoint where a user can register. In your case, I guess you just send the email the user wants, and your Symfony app will save this to the database.

B) Typically, step 2 would be that you would create some /login endpoint, where you would POST the username & password, and there, you would create & return a JWT. That is basically this step: https://knpuniversity.com/s.... However... because you don't have a password, I'm not sure if this makes sense. It may make more sense to just return the JWT in step (A), right after the user authenticates. The only tricky thing is how the user (your Android app) will fetch a new token later after the original JWT expires. I mean, you could make your JWT *never* expire... I guess... it depends on how secure your app needs to be :). Anyways, this is the one part that I'm not sure about... just because it's so odd to not have a password.

C) Finally, once the user has a JWT (either because you gave it to them in step A or step B), you create an authenticator that authenticates the request when the JWT is sent. We do that here: https://knpuniversity.com/s...

So, having no password shouldn't cause any problems. However, it's possible (I'm just not sure) that you may need to do a few more things "manually" versus using tools in the bundle. Really, this is what we do in this tutorial: we use the Lexik bundle... but mostly we only use its low-level features: we implement step (B) and (C) manually.

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
    }
}
userVoice