27

There are a few libraries for implementing JSON Web Tokens (JWT) in PHP, such as php-jwt. I am writing my own, very small and simple class but cannot figure out why my signature fails validation here even though I've tried to stick to the standard. I've been trying for hours and I'm stuck. Please help!

My code is simple

//build the headers
$headers = ['alg'=>'HS256','typ'=>'JWT'];
$headers_encoded = base64url_encode(json_encode($headers));

//build the payload
$payload = ['sub'=>'1234567890','name'=>'John Doe', 'admin'=>true];
$payload_encoded = base64url_encode(json_encode($payload));

//build the signature
$key = 'secret';
$signature = hash_hmac('SHA256',"$headers_encoded.$payload_encoded",$key);

//build and return the token
$token = "$headers_encoded.$payload_encoded.$signature";
echo $token;

The base64url_encode function:

function base64url_encode($data) {
    return rtrim(strtr(base64_encode($data), '+/', '-_'), '=');
}

My headers and payload perfectly match the validation site's default JWT, but my signature doesn't match so my token is flagged as invalid. This standard seems really straightforward so what's wrong with my signature?

BeetleJuice
  • 39,516
  • 19
  • 105
  • 165

3 Answers3

39

I solved it! I did not realize that the signature itself needs to be base64 encoded. In addition, I needed to set the last optional parameter of the hash_hmac function to $raw_output=true (see the docs. In short I needed to change my code from the original:

//build the signature
$key = 'secret';
$signature = hash_hmac('sha256',"$headers_encoded.$payload_encoded",$key);

//build and return the token
$token = "$headers_encoded.$payload_encoded.$signature";

To the corrected:

//build the signature
$key = 'secret';
$signature = hash_hmac('sha256',"$headers_encoded.$payload_encoded",$key,true);
$signature_encoded = base64url_encode($signature);

//build and return the token
$token = "$headers_encoded.$payload_encoded.$signature_encoded";
echo $token;
BradChesney79
  • 650
  • 7
  • 16
BeetleJuice
  • 39,516
  • 19
  • 105
  • 165
  • Does this work across networks, and different URLs...? generating this token on HTTP server and verifying on HTTPS server. its failing every time.. – MSQ Feb 13 '18 at 12:26
11

If you want to solve it using RS256 (instead of HS256 like OP) you can use it like this:

//build the headers
$headers = ['alg'=>'RS256','typ'=>'JWT'];
$headers_encoded = base64url_encode(json_encode($headers));

//build the payload
$payload = ['sub'=>'1234567890','name'=>'John Doe', 'admin'=>true];
$payload_encoded = base64url_encode(json_encode($payload));

//build the signature
$key = "-----BEGIN PRIVATE KEY----- ....";
openssl_sign("$headers_encoded.$payload_encoded", $signature, $key, 'sha256WithRSAEncryption'); 
$signature_encoded = base64url_encode($signature);

//build and return the token
$token = "$headers_encoded.$payload_encoded.$signature_encoded";
echo $token;

Took me way longer than I'd like to admit

Christian
  • 582
  • 9
  • 21
  • Solved my issue using "openssl_sign()". When i am trying to Signed using hash_hmac not able to descrypt key value properly. But now solved with using base64url_encode & openssl_sign. Thanks @Christian – Ganesh Jan 12 '22 at 15:21
2

https://github.com/gradus0/appleAuth look method $appleAuthObj->get_jwt_token()

<?php
include_once "appleAuth.class.php";

// https://developer.apple.com/account/resources/identifiers/list/serviceId -- indificator value
$clientId = ""; // com.youdomen
// your developer account id -> https://developer.apple.com/account/#/membership/
$teamId = "";
// key value show in -> https://developer.apple.com/account/resources/authkeys/list
$key = ""; 
// your page url where this script
$redirect_uri = ""; // example: youdomen.com/appleAuth.class.php
// path your key file, download file this -> https://developer.apple.com/account/resources/authkeys/list
$keyPath =''; // example: ./AuthKey_key.p8 

try{    
    $appleAuthObj = new \appleAuth\sign($clientId,$teamId,$key,$redirect_uri,$keyPath); 
    
    if(isset($_REQUEST['code'])){
        $jwt_token = $appleAuthObj->get_jwt_token($_REQUEST['code']);
        $response = $appleAuthObj->get_response($_REQUEST['code'],$jwt_token);
        $result_token = $this->read_id_token($response['read_id_token']);

        var_dump($response);
        var_dump($result_token);
    }else{
        $state = bin2hex(random_bytes(5));
        echo "<a href='".$appleAuthObj->get_url($state)."'>sign</a>";
    }
                                                                                    
} catch (\Exception $e) {
    echo "error: ".$e->getMessage();
}
gradus
  • 21
  • 2
  • 1
    Your answer could be improved with additional supporting information. Please [edit] to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Oct 02 '21 at 08:07