JSON Web Tokens without Firebase JWT

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';
    }
}

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";
}

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
}

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);
}

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);
    }
}

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.

Discussion (0)

Subscribe

pic

Upload image

TemplatesEditor guide

PersonalModerator

loading Create template

Templates let you quickly answer FAQs or store snippets for re-use.

SubmitPreviewDismiss

Code of ConductReport abuse

Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment's permalink.

Hide child comments as well

Confirm

For further actions, you may consider blocking this person and/or reporting abuse