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 …

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.


Print Share Comment Cite Upload Translate
APA
The Dev Drawer | Sciencx (2024-03-28T12:06:47+00:00) » JSON Web Tokens without Firebase JWT. Retrieved from https://www.scien.cx/2021/12/28/json-web-tokens-without-firebase-jwt/.
MLA
" » JSON Web Tokens without Firebase JWT." The Dev Drawer | Sciencx - Tuesday December 28, 2021, https://www.scien.cx/2021/12/28/json-web-tokens-without-firebase-jwt/
HARVARD
The Dev Drawer | Sciencx Tuesday December 28, 2021 » JSON Web Tokens without Firebase JWT., viewed 2024-03-28T12:06:47+00:00,<https://www.scien.cx/2021/12/28/json-web-tokens-without-firebase-jwt/>
VANCOUVER
The Dev Drawer | Sciencx - » JSON Web Tokens without Firebase JWT. [Internet]. [Accessed 2024-03-28T12:06:47+00:00]. Available from: https://www.scien.cx/2021/12/28/json-web-tokens-without-firebase-jwt/
CHICAGO
" » JSON Web Tokens without Firebase JWT." The Dev Drawer | Sciencx - Accessed 2024-03-28T12:06:47+00:00. https://www.scien.cx/2021/12/28/json-web-tokens-without-firebase-jwt/
IEEE
" » JSON Web Tokens without Firebase JWT." The Dev Drawer | Sciencx [Online]. Available: https://www.scien.cx/2021/12/28/json-web-tokens-without-firebase-jwt/. [Accessed: 2024-03-28T12:06:47+00:00]
rf:citation
» JSON Web Tokens without Firebase JWT | The Dev Drawer | Sciencx | https://www.scien.cx/2021/12/28/json-web-tokens-without-firebase-jwt/ | 2024-03-28T12:06:47+00:00
https://github.com/addpipe/simple-recorderjs-demo