DEV Community

Cover image for JSON Web Tokens without Firebase JWT
The Dev Drawer
The Dev Drawer

Posted on • Edited on

JSON Web Tokens without Firebase JWT

I have created a few tutorials on how to use Firebase JWT to create and use JSON Web Tokens, but I decided to try to do it without a framework, using just PHP. In this article, you can see how I created my own simple JWT generator.

View This On YouTube

Creating Our Class

class JWT {

    private $headers;
    private $secret;

    public function __construct()
    {
        $this->headers = [
            'alg' => 'HS256', // we are using a SHA256 algorithm
            'typ' => 'JWT', // JWT type
            'iss' => 'jwt.local', // token issuer
            'aud' => 'example.com' // token audience
        ];
        $this->secret = 'thisIsASecret';
    }
}
Enter fullscreen mode Exit fullscreen mode

Generate A Token

Within this class, we will create our generate function. this function will generate our token to be used against our validator.

public function generate(array $payload): string
{
    $headers = $this->encode(json_encode($this->headers)); // encode headers
    $payload["exp"] = time() + 60; // add expiration to payload
    $payload = $this->encode(json_encode($payload)); // encode payload
    $signature = hash_hmac('SHA256', "$headers.$payload", $this->secret, true); // create SHA256 signature
    $signature = $this->encode($signature); // encode signature

    return "$headers.$payload.$signature";
}
Enter fullscreen mode Exit fullscreen mode

Within our generate function, we reference a encode function. This function will simply base64 encode our strings to be used by the token.

private function encode(string $str): string
{
    return rtrim(strtr(base64_encode($str), '+/', '-_'), '='); // base64 encode string
}
Enter fullscreen mode Exit fullscreen mode

Validate Our Token

In this function, we take the generated token, and validate the strings, the encoding, and finally the time. I have it set for 1 minute so the token will expire after 1 minute to ensure it is used for a purpose.

public function is_valid(string $jwt): bool
{
    $token = explode('.', $jwt); // explode token based on JWT breaks
    if (!isset($token[1]) && !isset($token[2])) {
        return false; // fails if the header and payload is not set
    }
    $headers = base64_decode($token[0]); // decode header, create variable
    $payload = base64_decode($token[1]); // decode payload, create variable
    $clientSignature = $token[2]; // create variable for signature

    if (!json_decode($payload)) {
        return false; // fails if payload does not decode
    }

    if ((json_decode($payload)->exp - time()) < 0) {
        return false; // fails if expiration is greater than 0, setup for 1 minute
    }

    if (isset(json_decode($payload)->iss)) {
        if (json_decode($headers)->iss != json_decode($payload)->iss) {
            return false; // fails if issuers are not the same
        }
    } else {
        return false; // fails if issuer is not set 
    }

    if (isset(json_decode($payload)->aud)) {
        if (json_decode($headers)->aud != json_decode($payload)->aud) {
            return false; // fails if audiences are not the same
        }
    } else {
        return false; // fails if audience is not set
    }

    $base64_header = $this->encode($headers);
    $base64_payload = $this->encode($payload);

    $signature = hash_hmac('SHA256', $base64_header . "." . $base64_payload, $this->secret, true);
    $base64_signature = $this->encode($signature);

    return ($base64_signature === $clientSignature);
}
Enter fullscreen mode Exit fullscreen mode

Putting It All Together

class JWT
{
    private $headers;

    private $secret;

    public function __construct()
    {
        $this->headers = [
            'alg' => 'HS256', // we are using a SHA256 algorithm
            'typ' => 'JWT', // JWT type
            'iss' => 'jwt.local', // token issuer
            'aud' => 'example.com' // token audience
        ];
        $this->secret = 'thisIsASecret'; // change this to your secret code
    }

    public function generate(array $payload): string
    {
        $headers = $this->encode(json_encode($this->headers)); // encode headers
        $payload["exp"] = time() + 60; // add expiration to payload
        $payload = $this->encode(json_encode($payload)); // encode payload
        $signature = hash_hmac('SHA256', "$headers.$payload", $this->secret, true); // create SHA256 signature
        $signature = $this->encode($signature); // encode signature

        return "$headers.$payload.$signature";
    }

    private function encode(string $str): string
    {
        return rtrim(strtr(base64_encode($str), '+/', '-_'), '='); // base64 encode string
    }

    public function is_valid(string $jwt): bool
    {
        $token = explode('.', $jwt); // explode token based on JWT breaks
        if (!isset($token[1]) && !isset($token[2])) {
            return false; // fails if the header and payload is not set
        }
        $headers = base64_decode($token[0]); // decode header, create variable
        $payload = base64_decode($token[1]); // decode payload, create variable
        $clientSignature = $token[2]; // create variable for signature

        if (!json_decode($payload)) {
            return false; // fails if payload does not decode
        }

        if ((json_decode($payload)->exp - time()) < 0) {
            return false; // fails if expiration is greater than 0, setup for 1 minute
        }

        if (isset(json_decode($payload)->iss)) {
            if (json_decode($headers)->iss != json_decode($payload)->iss) {
                return false; // fails if issuers are not the same
            }
        } else {
            return false; // fails if issuer is not set 
        }

        if (isset(json_decode($payload)->aud)) {
            if (json_decode($headers)->aud != json_decode($payload)->aud) {
                return false; // fails if audiences are not the same
            }
        } else {
            return false; // fails if audience is not set
        }

        $base64_header = $this->encode($headers);
        $base64_payload = $this->encode($payload);

        $signature = hash_hmac('SHA256', $base64_header . "." . $base64_payload, $this->secret, true);
        $base64_signature = $this->encode($signature);

        return ($base64_signature === $clientSignature);
    }
}
Enter fullscreen mode Exit fullscreen mode

Conclusion

It is a simple project, but works as intended. I have used Firebase JWT in the past but sometimes you just need a simple solution without having to import and entire library. I hope this helps for your next small project. You can download the class on my GitHub or subscribe on YouTube for more tutorials.

Read more articles on DevDrawer

Top comments (2)

Collapse
 
fortunatus profile image
Fortunatus Adegoke

This is a good minified JWT authentication good job.

Will be grate if you could explain how to connect it to a CRUD REST API for authorisation purposes.

Collapse
 
thedevdrawer profile image
The Dev Drawer

I think one of my previous video tutorials may be helpful with this:
Create JSON Using PHP and MySQL, A Simple API youtu.be/m8rnFHd2zcY
JS Fetch with PHP RESTful API, Login Authentication, youtu.be/5ujpgk4oe3c

I don't have a written tutorial, but those 2 videos should go over creating a simple API then using it for authentication.

I hope that helps.