How to Create a JSON Web Token Using PHP
Rob Waller

Rob Waller @robdwaller

About: I am a developer with a passion for testing. I've been coding for 14 years and I want to share my experience and learnings with other developers to help them write better software.

Location:
Aylesbury, UK
Joined:
May 14, 2017

How to Create a JSON Web Token Using PHP

Publish Date: Jan 31 '18
96 32

I have spent the last year intermittently working on a PHP JSON Web Token library called ReallySimpleJWT, and this week I released version 1.0.0. The code is accessible via GitHub and Packagist.

For those of you who have not used JSON Web Tokens before they are a URL friendly, token based, authentication system. And they allow you to easily transfer information via an encoded JSON payload.

The core benefits of JSON Web Tokens are twofold: you don't need to use sessions or cookies to maintain authentication between states; and you don't have to constantly call the database for user information as this can be stored in the token payload.

Each token is broken down into three parts and each part is separated by a dot.

  • Header: This contains information on the token type, usually JWT, and the hashing algorithm used, eg HMAC SHA256 or RSA.
  • Payload: This contains any information you wish to transfer about the user, eg the user identifier.
  • Signature: This secures the token and is a hash of the encoded header and payload, along with a secret.
// Token structure
header.payload.signature

// A real world token
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

JWT security is achieved via the signature which is created by hashing the encoded header and payload and securing this with a secret only known to the author.

When receiving a token from a user the author will then be able to validate the signature by re-hashing the received header and payload with the known secret and checking it matches the received signature. If anyone were to tamper with the header or payload the signatures would not match and authentication would fail.

If you wish to get started quickly with JWTs the ReallySimpleJWT library offers an easy to use interface for generating and validating JSON Web Tokens.

use ReallySimpleJWT\Token;

// Generate a token
$token = Token::getToken('userIdentifier', 'secret', 'tokenExpiryDateTimeString', 'issuerIdentifier');

// Validate the token
$result = Token::validate($token, 'secret');

It's perfect if you need to quickly implement user authentication on a simple API. The library also offers more advanced usage and functionality if you'd like to read the documentation.

How to Build a JSON Web Token in PHP

If you'd like to build your own JWT generator or just learn a little bit more about them the following guide will help. While the examples below are written using PHP the concepts apply to any language so all developers should find them useful. The full script is at the bottom of this guide.

Create the Header and Payload

To begin we need to create header and payload JSON strings. We'll do this based on two simple arrays each asserting a number of claims about the token. You can read more about claims in the associated RFC. For the header we define the type typ and the algorithm alg claims which are RFC standard claims; for the payload we'll create our own claim user_id.

// Create token header as a JSON string
$header = json_encode(['typ' => 'JWT', 'alg' => 'HS256']);

// Create token payload as a JSON string
$payload = json_encode(['user_id' => 123]);

Create Base64Url Header and Payload Strings

Next we encode our $header and $payload JSON strings as Base64Url strings. This is slightly different to a standard Base64 string and there is no built in PHP Base64Url method yet. So we have to do a bit of string replace magic which will replace + with -, / with _ and = with ''. This is so that the Base64 string is passed within URLs without any URL encoding.

// Encode Header to Base64Url String
$base64UrlHeader = str_replace(['+', '/', '='], ['-', '_', ''], base64_encode($header));

// Encode Payload to Base64Url String
$base64UrlPayload = str_replace(['+', '/', '='], ['-', '_', ''], base64_encode($payload));

Create the Signature

To create the signature we need to use the hash_hmac() method available in PHP and use the sha256 algorithm. We pass in a concatenated string of the Base64Url encoded header and payload $base64UrlHeader . "." . $base64UrlPayload. It's important to note we have to include the dot . between the two strings. We add a secret, ideally a strong one that is longer than twelve characters. The ReallySimpleJWT library enforces this principle, but for our example we don't need to worry. Finally we force the hash_hmac() method to return the output as binary data.

// Create Signature Hash
$signature = hash_hmac('sha256', $base64UrlHeader . "." . $base64UrlPayload, 'abC123!', true);

Base64Url Encode the Signature

Once we have created the signature we simply need to Base64Url encode it as we did with the header and payload.

// Encode Signature to Base64Url String
$base64UrlSignature = str_replace(['+', '/', '='], ['-', '_', ''], base64_encode($signature));

Create the JSON Web Token

Finally we create the JWT by concatenating the header $base64UrlHeader, payload $base64UrlPayload and signature $base64UrlSignature. Each part of the JWT is separated by a dot.

// Create JWT
$jwt = $base64UrlHeader . "." . $base64UrlPayload . "." . $base64UrlSignature;

// Output
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxMjN9.NYlecdiqVuRg0XkWvjFvpLvglmfR1ZT7f8HeDDEoSx8

And that's it, really easy. You can test the JWT that this code produces on the JWT.io website. The code is below in full and I'd suggest you read the relevant documentation on the JWT site along with the RFC.

You can of course use the ReallySimpleJWT Library if you wish and I will produce a post on validating JWTs in the next week or two. If you have any thoughts or have noticed any mistakes please message me @RobDWaller on Twitter.

The Script

// Create token header as a JSON string
$header = json_encode(['typ' => 'JWT', 'alg' => 'HS256']);

// Create token payload as a JSON string
$payload = json_encode(['user_id' => 123]);

// Encode Header to Base64Url String
$base64UrlHeader = str_replace(['+', '/', '='], ['-', '_', ''], base64_encode($header));

// Encode Payload to Base64Url String
$base64UrlPayload = str_replace(['+', '/', '='], ['-', '_', ''], base64_encode($payload));

// Create Signature Hash
$signature = hash_hmac('sha256', $base64UrlHeader . "." . $base64UrlPayload, 'abC123!', true);

// Encode Signature to Base64Url String
$base64UrlSignature = str_replace(['+', '/', '='], ['-', '_', ''], base64_encode($signature));

// Create JWT
$jwt = $base64UrlHeader . "." . $base64UrlPayload . "." . $base64UrlSignature;

echo $jwt;

Comments 32 total

  • Ruben
    RubenJan 31, 2018

    Hi Rob,

    Great work! Looks easy to use. However, what's the advantage of your library over something like firebase/php-jwt?

    • Rob Waller
      Rob WallerFeb 1, 2018

      Hi, thanks for the response.

      I would say my library has a simpler and more eloquent interface. Also the code in my library is more abstracted so should be more robustly tested.

      A weakness would be that ReallySimpleJWT hasn't been as widely used therefore in terms of community feedback and integration testing it's probably not as robust.

      I would though very much appreciate any feedback you have on my code, feel free to open some issues if you think it necessary.

      Cheers, Rob

  • Jeremy
    JeremyMar 15, 2018

    Hi Rob,
    You did a goog job !
    I'm pretty new to php development, so i have some questions.
    Let's say that I have an ios app, which sends to the server
    Username = john doe
    Password = helloWorld
    Whats going on next ?'
    What would be the php code for that in order to create the jwt.
    And so, what would be userId, secret, TimedateString and issuer identifier ?
    Thksss for what you did Rob !

    • Rob Waller
      Rob WallerMar 17, 2018

      Thanks for the comment, I will try to answer your question.

      The username and password, would query the database to check that the user exists. If the user does exist the user identifier for the database table will be retrieved, this is usually an integer. You would then use the identifier to create your token so you know who the user is.

      The secret is what you want to hash your token with, it's like a salt, it's for security purposes. The data time string is for the expiry date, when do you want the token to expire, the issue identifier is a reference to the website that generated the token, this could be a URL for example.

      I hope that info helps. Let me know if you have any further questions.

      • Jeremy
        JeremyMar 17, 2018

        Thanks for your fast answer, i understood more now ! But I still have few questions 🙈
        When I want to create my token, I write for example
        Token::getToken('24', 'sha256, 1*,2*)
        1*) According to you, what is the best dateExpiry ?
        2*) When you say that the issue identifier is a reference to the website that generated the token, what do you mean by that ? I thought that was your method who created the toked.
        And if the meaning of "generated" is the side that ask for creating the token (i.e my iOS app), is the issue id the bundle id of the App ?
        Ps: thanks you a lot, really, and sorry for my misunderstanding
        Of words, i'm new to the english too :)

        • Rob Waller
          Rob WallerMar 18, 2018

          The expiry should be relatively short, I would say minutes. You should also create a way for you to update tokens as Facebook does. Facebook tokens last for about 60 minutes and if you want to continue making requests after 60 minutes you have to trade the current token for a new token before the current token expires.

          The issue identifier is the application that creates the token, not the application or user who asks for the token.

          eg

          User 1 asks for a token

          Website A creates and returns the token to User 1.

          In this scenario the issue identifier would be "Website A"

  • Sujit Singh
    Sujit SinghJul 4, 2018

    Hi Rob, how to use JWT in core PHP.

    • Rob Waller
      Rob WallerJul 4, 2018

      I don't believe JWT is built into the core of PHP, someone would need to write an extension.

  • jan__irving
    jan__irvingJul 31, 2018

    @rob , how can I use the jwt with ES256 algorithm? Thank you.

    • Rob Waller
      Rob WallerAug 4, 2018

      I don't believe I've looked into this yet, if you submit an issue to my repo I can take a look though.

  • Kieren Christopher
    Kieren ChristopherAug 2, 2018

    Great tut, Rob! Thanks

  • datelligence
    datelligenceAug 16, 2018

    Thanks Rob, great and unique tutorial for generating web tokens without dependencies, thanks for sharing.

  • Mohammad
    MohammadAug 17, 2018

    "if you wish and I will produce a post on validating JWTs in the next week or two"
    I liked your post, please start the post on validating JWTs :(
    thanks

  • olgertkranga
    olgertkrangaAug 28, 2018

    Hi Rob! Many thanks. Very very very good!
    :)

  • Zohaib
    ZohaibJan 16, 2019

    unable to install pakage on php 5.6.30?

    Got an error below.
    Problem 1
    - paragonie/random_compat v9.99.99 requires php 7 -> your PHP version (5.6.
    30) does not satisfy that requirement.
    - paragonie/random_compat v9.99.99 requires php 7 -> your PHP version (5.6.
    30) does not satisfy that requirement.
    - Installation request for paragonie/random_compat (locked at v9.99.99) -> s
    atisfiable by paragonie/random_compat[v9.99.99].

  • Sagar Gurnani
    Sagar GurnaniMar 29, 2019

    I followed your blog topic i.e. "How to Build a JSON Web Token in PHP" in order to generate a JWT token. But, when I try to verify it via the available JWT verifiers (such as jwt.io/) I get the "Invalid Signature" error.

    Maybe the checker is buggy. Can you suggest a JWT checker that you use, please? If my token is genuinely invalid, can you suggest some routes to follow so that I can discover what I am doing wrongly?

    • Rob Waller
      Rob WallerApr 4, 2019

      When using jwt.io are you providing them with the correct secret?

      Also if you're worried this is an 'issue' with the library feel free to create a ticket with an example token and I'll take a closer look.

      github.com/RobDWaller/ReallySimple...

    • Bob Rundle
      Bob RundleOct 4, 2022

      I think this is because the jwt.io checker is a bit counter intuitive. To get the signature to verify you need to paste the secret into the "verify signature" block and also have the "secret base64 encoded" checkbox set properly. If your secret is simply text you leave this check off. If it is binary then you need to base64 encode it before pasting it. Then set this check on.

      The JWTs I generated with Rob's code verified fine on jwt.io.

  • Mohamed SALL
    Mohamed SALLMay 16, 2019

    Hi Rob,

    Thanks for this damn great job !
    This really help me.

  • tmblog
    tmblogAug 12, 2019

    Hi Rob, where to save the JWT in order to post back to a protected page? Anything other than localStorage or cookie?

  • yetanotherjim
    yetanotherjimSep 21, 2019

    Any idea why the jwt created using this method does not match the jwt created on jwt.io? They appear valid and the payload appears the same but the tokens themselves don't match.

    • yetanotherjim
      yetanotherjimSep 21, 2019

      Nope, my bad. I was looking at everything except the order of the header entries. Once I got them straight, they are identical. Great job!

  • niemeier23
    niemeier23Dec 16, 2019

    Hi, Rob. Thanks for the post.

    Regarding the secret. How does that get managed on the server-side, between JWT creations and validations? Is it a static config string? Is a new one supposed to be generated for each unique user/session? Or, can I use the same secret (salt) for everything, as long as it's sufficiently complex?

    This is something that most JWT articles/explanations omit, and perhaps it's taken for granted that a lot of developers haven't implemented an authorization system before, and therefore aren't sure how not to shoot themselves in the foot, security-wise.

    Thanks.

    • pMatt1988
      pMatt1988Oct 6, 2020

      Hello neimeier. What I have done for the secret is to add it to the user's row in the database. You will use the payload of the jwt to store the username/user id and when the user attempts to authenticate, you can verify the jwt against the secret stored in the users database row. This makes it easier to invalidate tokens as well, since if the user resets their secret in the database, every device connected will have to authenticate again.

  • Hamid Semix
    Hamid SemixDec 17, 2019

    Nice Article

  • accorinti
    accorintiApr 4, 2020

    Hello, ist there a way to use your library in my php scripts without the installer, with the good old php require_once for example?

    • Rob Waller
      Rob WallerApr 4, 2020

      In theory you can download the repo into your own project and require_once all the files you need. Ultimately that is all the Composer autoloader does.

  • Mario Russo
    Mario Russo Nov 2, 2020

    How would you decode this jwt? to extract the information if needed.

  • Gustavo
    GustavoDec 23, 2020

    Rob, if I use your package, how do I get the actual generated JWT to send over a request, for instance?

  • dov79
    dov79Sep 19, 2022

    Excellent article, thanks! Helped me build a JWT token in Zoho Deluge for Zoom API.

    Only point that seems to be different for Zoom API is this stage should be omitted.

    // Encode Signature to Base64Url String

    For Zoom the signature is added without further Base64 encryption.

  • Babacar Cisse DIA
    Babacar Cisse DIASep 6, 2023

    if you are using public/private key pair you can use the script below

    <?php
    
    // Create token header as a JSON string
    $header = json_encode(['typ' => 'JWT', 'alg' => 'RS256']);
    
    // Create token payload as a JSON string
    $payload = json_encode([
        'exp' => now()->add('day', 30)->timestamp,
        'iss' => 'abc123',
        'sub' => 'something123'
    ]);
    
    
    // Encode Header to Base64Url String
    $base64UrlHeader = str_replace(['+', '/', '='], ['-', '_', ''], base64_encode($header));
    
    // Encode Payload to Base64Url String
    $base64UrlPayload = str_replace(['+', '/', '='], ['-', '_', ''], base64_encode($payload));
    
    $private_key = <<<EOD
    -----BEGIN PRIVATE KEY-----
    INSERT HERE
    -----END PRIVATE KEY-----
    EOD;
    
    $signature = "";
    $algo = "SHA256";
    openssl_sign($base64UrlHeader . "." . $base64UrlPayload, $signature, $private_key, $algo);
    
    // Encode Signature to Base64Url String
    $base64UrlSignature = str_replace(['+', '/', '='], ['-', '_', ''], base64_encode($signature));
    
    // Create JWT
    $jwt = $base64UrlHeader . "." . $base64UrlPayload . "." . $base64UrlSignature;
    
    // Output
    //eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxMjN9.NYlecdiqVuRg0XkWvjFvpLvglmfR1ZT7f8HeDDEoSx8
    
    
    Enter fullscreen mode Exit fullscreen mode
Add comment