Buy
Buy

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!

  • 2018-09-11 Shaun

    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 :)

  • 2018-09-10 weaverryan

    Hey Shaun!

    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!

  • 2018-09-07 Shaun

    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"

  • 2018-09-07 weaverryan

    Hey Shaun!

    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/Le... - 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!

  • 2018-09-06 Shaun

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

  • 2018-09-06 Diego Aguiar

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

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

  • 2018-09-05 Shaun

    Hey Diego Aguiar

    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)"} []

  • 2018-09-05 Diego Aguiar

    Hey Shaun

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

    Cheers!

  • 2018-09-05 Shaun

    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 ###
  • 2018-07-27 Diego Aguiar

    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!

  • 2018-07-27 Jazz

    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 ...

  • 2018-07-16 Victor Bocharsky

    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!

  • 2018-07-15 argy_13

    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

  • 2018-06-20 weaverryan

    Hey Ingvald Orbeck!

    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!

  • 2018-06-19 Ingvald Orbeck

    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?

  • 2018-06-19 weaverryan

    Hey Ingvald Orbeck!

    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!

  • 2018-06-19 Ingvald Orbeck

    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

  • 2016-10-21 John Armstrong

    That was me :)
    Thanks for your help and awesome tutorials!

  • 2016-10-21 weaverryan

    Hey John!

    I was late on this - but it looks like Robin (bundle maintainer) already answered. For others: https://github.com/lexik/Le...

    Cheers!

  • 2016-10-19 John Armstrong

    Hello KNPU,

    I'm trying to upload my symfony web/api app to Heroku, but it fails with this error:

    Invalid configuration for path "lexik_jwt_authentication.private_key_path": The file "\/tmp\/build_a639bed6c50d79ea912b8fd3d5461615\/app\/..\/var\/jwt\/private.pem" doesn't exist or is not readable.
    remote: If the configured encoder doesn't need this to be configured, please don't set this option or leave it null.

    Please help!

  • 2016-09-15 weaverryan

    Hey Chuck!

    Actually, it looks like you found a bug! I've just fixed it here: https://github.com/knpunive...

    We use this bundle a little non-traditionally, choosing to build out parts of the security system ourselves, instead of letting the bundle do it for us. In fact, the token_ttl in config.yml is *never* used because we're creating the token ourselves. *We* need to manually set the expiration when creating the token.

    Sorry about that oversight! But thanks for reporting it - I'll be adding a note to the text and video about it!

    Thanks!

  • 2016-09-14 Chuck Norris

    Hi Ryan,

    Just finished this awesome tutorial series.

    But I still have some questions.
    One of them is about the token_ttl. In my config, I set it to 3600, so, normally, the generated token is valid only for an hour.
    But, if I create a token and I login with Postman or with a JS frontend (React in my case), I still can use the same Token even after a day.

    With Postman, the token is directly written in the params header in every request, and with react, the token is saved in localStorage.

    Is it normal? Do I missed something ?