How to generate JWT token in php using with the following parameters
Subject, Issuer, Expiry time and payload in the < PAYLOAD > tag.
Id can be any random number of any length.
subject is TestService
Issuer is Baguma Inc
Expiry Time will be 30 sec from current time(ideally ).
Payload is the request from Third Party
SigningKEY is fcvxcnfrhrtghkfghgwerikdf
Signature algorithm will be HS512.
Sample request from Third Party is shown below
<COMMAND><TYPE>REQUEST</TYPE><INTERFACE>TESTACCOUNT</INTERFACE> <REQUESTID>123</REQUESTID></COMMAND
Your answer got me started. Here's the working code I went with (albeit generalized from your specific case). Thanks for getting me started!
function gen_jwt():String{
$signing_key = "changeme";
$header = [
"alg" => "HS512",
"typ" => "JWT"
];
$header = base64_url_encode(json_encode($header));
$payload = [
"exp" => 0,
];
$payload = base64_url_encode(json_encode($payload));
$signature = base64_url_encode(hash_hmac('sha512', "$header.$payload", $signing_key, true));
$jwt = "$header.$payload.$signature";
return $jwt;
}
/**
* per https://stackoverflow.com/questions/2040240/php-function-to-generate-v4-uuid/15875555#15875555
*/
function base64_url_encode($text):String{
return str_replace(['+', '/', '='], ['-', '_', ''], base64_encode($text));
}
With the help of an article from DZone Security, I managed to generate a JWT token by doing the following
Define the base64UrlEncode function which replaces + with -, / with _ and = with ''.
function base64UrlEncode($text)
{
return str_replace(
['+', '/', '='],
['-', '_', ''],
base64_encode($text)
);
}
Encode the headers using base64UrlEncode
$headers = [ "alg" => "HS512"];
$headers_encoded = $this->base64url_encode(json_encode($headers));
Encode the Payload using Base64 URL encode as well
$issuedAt = time();
$payload = [
"id" =>$this->gen_uuid(), // .setId(UUID.randomUUID().toString())
"sub"=> "TestService", //Subject
"exp"=> $issuedAt+30,
"iss"=> "Baguma Inc", //issuer
"iat"=> $issuedAt, //issued at
"PAYLOAD"=> "<COMMAND><TYPE>REQUEST</TYPE><INTERFACE>TESTACCOUNT</INTERFACE> <REQUESTID>123</REQUESTID></COMMAND"];
$payload_encoded = $this->base64url_encode(json_encode($payload));
Using the Key/secret build the signature
$key = "fcvxcnfrhrtghkfghgwerikdf"
$signature = hash_hmac('sha512',"$headers_encoded.$payload_encoded",$key,true);
Build and return the token
$token = "$headers_encoded.$payload_encoded.$signature_encoded";
Related
I am trying to get the jwt token, but i am getting errors everytime with everything that i try. Below are th things that i have tried. I do get the jwt-token without the package but when i use the jwt.io to check the signature verification,it fails every time. With the package i get different errors like the private key cannot be coereced and sometimes invalid algorithm. Please correct me where i am messing it up.
Without php-Jwt
$header = [
'alg' => "RS384",
'typ' => "JWT"
];
$payload = [
'iss' => 'my-client-id',
'sub' => 'my-client-id',
'aud' => "https://fhir.epic.com/interconnect-fhir-oauth/oauth2/token",
'jti' => (string)strtotime(gmdate("Y-m-d H:i:s")),
'exp' => strtotime(gmdate("Y-m-d H:i:s")) + 270,
];
$privateKey = "my private Key"
$headers_encoded = $this->base64url_encode(json_encode($header));
$payload_encoded = $this->base64url_encode(json_encode($payload));
$signature = hash_hmac('sha384', $headers_encoded.'.'.$payload_encoded, $privateKey, true);
// Encode the signature as a base64url string
$signature_encoded = $this->base64url_encode($signature);
$jwt = $headers_encoded.'.'.$payload_encoded.'.'.$signature_encoded;
- With php-jwt
installed the package
composer require firebase/php-jwt --ignore-platform-req=ext-mongodb
Used the required files in my controller
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
Tried Encoding:
$check = JWT::encode(
$header,
$payload,
$privateKey,
'RS384'
);
i get different errors like the private key cannot be coereced and sometimes invalid algorithm. Please correct me where i am messing it up.
In simple words, this is what I am doing or trying to do. A generic form of my code:
<?php
// Load the private key from a file
$privateKey = file_get_contents('private.key');
// Set the header and payload for the JWT
$header = [
'alg' => 'RS384',
'typ' => 'JWT'
];
$payload = [
'sub' => '1234567890',
'name' => 'John Doe',
'iat' => 1516239022
];
// Encode the header and payload as JSON strings
$headerEncoded = base64_encode(json_encode($header));
$payloadEncoded = base64_encode(json_encode($payload));
// Concatenate the header, payload, and secret to create the signature
$signature = hash_hmac('sha384', "$headerEncoded.$payloadEncoded", $privateKey, true);
// Encode the signature as a base64 string
$signatureEncoded = base64_encode($signature);
// Concatenate the header, payload, and signature to create the JWT
$jwt = "$headerEncoded.$payloadEncoded.$signatureEncoded";
echo $jwt;
I do get the jwt-signature but on https://jwt.io/ it shows unverified.
Fixed
Use this instead of hash_mac():
openssl_sign($data, $signature, $privateKey, OPENSSL_ALGO_SHA384);
Make sure if the key is stored in PHP variable, format the key like this:
$privateKey = "------BEGIN PRIVATE KEY------\n".
"MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQC7VJTUt9Us8cKj\n".
"MzEfYyjiWA4R4/M2bS1GB4t7NXp98C3SC6dVMvDuictGeurT8jNbvJZHtCSuYEvu\n".
.
.
.
"TQrKhArgLXX4v3CddjfTRJkFWDbE/CkvKZNOrcf1nhaGCPspRJj2KUkj1Fhl9Cnc\n".
"dn/RsYEONbwQSjIfMPkvxF+8HQ==\n".
"------END PRIVATE KEY------";
enclose every line in Double quotes "" and add \n at end of every line.
I'm close but jwt.io doesn't like the signature I generate. With this code I generate the following JWT. If this ain't the way, how should I be generating a JWT in PHP if I can't use external libraries?
function gen_jwt():String{
$signing_key = "changeme";
// header always eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9
$header = [
"alg" => "HS512",
"typ" => "JWT"
];
$header = base64_url_encode(json_encode($header));
log_message('debug',__CLASS__.'('.__FUNCTION__.':'.__LINE__.') json base64_url_encode: ' . $header);
// test case 0 generates (on jwt.io):
// with secret base64 encoded: eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJleHAiOjB9.ZW0gOCJV4e1KgGEsw0bL7oCF1AI1PBL8VVgSoss4tmr7682p6DpNc1uGbBpOEfkPjKJv0JBnLvjH2XUbo8PHUg
// without secret b64 encoded: eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJleHAiOjB9.pqzfdCTmr-eWW9sEgV-COCdS4dI7MDpCIFWss6kXnAC9eLdGX1qOOr8BtJih59o_U_AdHtBh8JwUQ4dEPTk0rg
$payload = [
// "exp" => time() + ...,
"exp" => 0,
];
$payload = base64_url_encode(json_encode($payload));
$signature = hash_hmac('sha512', "$header.$payload", $signing_key, false);
log_message('debug',__CLASS__.'('.__FUNCTION__.':'.__LINE__.') signature: ' . $signature);
$signature = base64_url_encode($signature);
log_message('debug',__CLASS__.'('.__FUNCTION__.':'.__LINE__.') signature: ' . $signature);
// all three parts b64 url-encoded
$jwt = "$header.$payload.$signature";
log_message('debug',__CLASS__.'('.__FUNCTION__.':'.__LINE__.') jwt: ' . $jwt);
return $jwt;
}
/**
* per https://stackoverflow.com/questions/2040240/php-function-to-generate-v4-uuid/15875555#15875555
*/
function base64_url_encode($text):String{
return str_replace(
['+', '/', '='],
['-', '_', ''],
base64_encode($text)
);
}
/**
* per https://www.uuidgenerator.net/dev-corner/php
*/
function guidv4($data = null): String {
// Generate 16 bytes (128 bits) of random data or use the data passed into the function.
$data = $data ?? random_bytes(16);
assert(strlen($data) == 16);
// Set version to 0100
$data[6] = chr(ord($data[6]) & 0x0f | 0x40);
// Set bits 6-7 to 10
$data[8] = chr(ord($data[8]) & 0x3f | 0x80);
// Output the 36 character UUID.
return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4));
}
Comes out:
eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJleHAiOjB9.ZmIxNzIyN2Q2ZjFhYjg3ZTJjMTY0NDJkNGQ4NzFlYWFmMjFhYzg1NzI5NGRkOGVhZmY4MTYzNWI1YTMyYWEyN2UxOTFmN2E5MzA1ZTZjZmI0OGVlZmMwN2U2NTc1MzNhZDg0NmMxMTZhZDZlOGVlYjJmMGVmOWUxOTMyYzE5MmE
...which jwt.io (and my own decoding efforts) say is an invalid signature. Help? Thanks!
I guess the trick was to base64-url-encode the binary output of the hmac like...
$signature = base64_url_encode(hash_hmac('sha512', "$header.$payload", $signing_key, true));
So the copy-paste-able code would be:
function gen_jwt():String{
$signing_key = "changeme";
$header = [
"alg" => "HS512",
"typ" => "JWT"
];
$header = base64_url_encode(json_encode($header));
$payload = [
"exp" => 0,
];
$payload = base64_url_encode(json_encode($payload));
$signature = base64_url_encode(hash_hmac('sha512', "$header.$payload", $signing_key, true));
$jwt = "$header.$payload.$signature";
return $jwt;
}
/**
* per https://stackoverflow.com/questions/2040240/php-function-to-generate-v4-uuid/15875555#15875555
*/
function base64_url_encode($text):String{
return str_replace(['+', '/', '='], ['-', '_', ''], base64_encode($text));
}
I'm trying to encode a message in flutter and to verify it with some php code, but somehow there are some differences in the hmac encoding. The header and payload values are the same in both cases. But somehow there are some differences between the resulting values.
Any help would be very helpful, I'm stuck on this for some days now.
$base64UrlHeader = 'header';
$base64UrlPayload = 'payload';
$secret = 'secret';
$signature = hash_hmac('sha256', $base64UrlHeader . "." . $base64UrlPayload, $secret, true); // 9b491a7aa29955d9d67e302965665ba0cfa4306c00470f8946eb6aa67f676595
$base64UrlSignature = base64UrlEncode($signature); // sYql52zk6tqYeGSUsDv_219UtgpK3c8-TMuko4n_L5Q
function base64UrlEncode($text) {
return str_replace(
['+', '/', '='],
['-', '_', ''],
base64_encode($text)
);
}
And this is my dart code:
// This values are obtained by using the _base64UrlEncode method from below,
// I just wrote the values directly here not to clutter with code
var base64UrlHeader = 'header';
var base64UrlPayload = 'payload';
/// SIGNATURE
var secret = utf8.encode('secret');
var message = utf8.encode(base64UrlHeader + '.' + base64UrlPayload);
var hmac = new Hmac(sha256, secret);
var digest = hmac.convert(message); // b18aa5e76ce4eada98786494b03bffdb5f54b60a4addcf3e4ccba4a389ff2f94
var signature = _base64UrlEncode(digest.toString()) // YjE4YWE1ZTc2Y2U0ZWFkYTk4Nzg2NDk0YjAzYmZmZGI1ZjU0YjYwYTRhZGRjZjNlNGNjYmE0YTM4OWZmMmY5NA
// This method is used to obtain the header, payload and signature
static String _base64UrlEncode(String text) {
Codec<String, String> stringToBase64Url = utf8.fuse(base64url);
return stringToBase64Url
.encode(text)
.replaceAll('=', '')
.replaceAll('+', '-')
.replaceAll('/', '_');
}
Both the header and payload are obtained from the same json object,
Given this code:
function base64url_encode($data) {
return rtrim(strtr(base64_encode($data), '+/', '-_'), '=');
}
$key = 'secret';
//setting the header: 'alg' => 'HS256' indicates that this token is signed using HMAC-SHA256
$header = array(
'alg' => 'HS256',
'typ' => 'JWT'
);
// Returns the JSON representation of the header
$header = json_encode($header);
//encodes the $header with base64.
$header = base64url_encode($header);
$payload = array("a" => "b");
$payload = json_encode($payload);
$payload = base64url_encode($payload);
$signature = hash_hmac('SHA256','$header.$payload', $key, true);
$signature = base64url_encode($signature);
echo "$header.$payload.$signature";
Returns the following JWT:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhIjoiYiJ9.rhCKIvkwiuNcchxDZnGak8XT1q8lmLhnm8aIxzUioWg
But the signature is not verified at https://jwt.io/
The payload is decrypted well though... What may be the problem ?
You are using single quotes around $header.payload when calculating the HMAC, rather than double quotes; the former uses the literal string and does not expand the variables:
$signature = hash_hmac('SHA256', "$header.$payload", $key, true);
I am having trouble creating a JWT for Google Drive authorization.
The instruction is given in this link.
<?php
//helper function
function base64url_encode($data) {
return rtrim(strtr(base64_encode($data), '+/', '-_'), '=');
}
//Google's Documentation of Creating a JWT https://developers.google.com/identity/protocols/OAuth2ServiceAccount#authorizingrequests
$raw_key_data="-----BEGIN PRIVATE KEY-----\nMIIEvAIBADANBgkWhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCLJN7B2USp0bTH\n7aVt1K/pAJa9rgUEkebbRQUPEopManZHgJK8SMzq5nUM3OOVlTdyDyuzxUZW65Y+\nR1xajNHSidE+rJJ3MWwuevXBuUws5oiA+DXJBB6FsTtHDvUPT7sePbJx8Y80S1Jr\nB3apJNdohjSef4UihgjJ93A0PgeSVz7CeUKxrBHdGiK2Ikf7wTCzcRzz/15I6QLu\ncsNbWxo0+koXo9D0+wT3RN+riSyTa2WwYn//B8v6VmDPVrcjxETEXraAIMpqURtK\n4BuK4Qp2pSd1u2zrGc58BVGuT6+nPVpmX6wiZ21sqOulTzgIOXKIuc4zdKvZ+jz/\n1jst3ertAgMBAAECggEADRGn+IYbLGYdeD/Kb2/wG87p2aP8Jas8hzDK4lkH81h2\nho29eoDN+mwt50jh+V08CXMCVE69phFXmb7jHkAmvwMhy6Sy1w4lzpHO/mSUko0O\nmip2BszjvwPgAPMXMlp3RUZfOdOJ80v10Eaxrv5eWxtr2s04aH81WR7sA4Ql+uki\nbTiLbo4odpMVPkoJZATZKQd+L/bBM4a+b3IM87/TE7skMrSgQE6cnjjTI5Uk+WSl\nxjiZvj5XmticW3vPavL/ZXPEZqy5IxvcxdF5rGHCHVu4ah0CmDdc9A3jF0flhewK\nV6mOViqbKGInnMn4kt3l4C3+wF2+dge6t8BQ/TMtwwKBgQDBnpjiC+wRVQy36KCs\ntYdZ60ykuQeKqMyACt6FvC74xy+PjLVntxDaYArva2PVaZSm9B2FgGHPz9AfW80/\n1ZeNHcuwymni8n+4MNpQbJuhuHK8UjSo0rFq6Nrddi9RCjzASSX8s4Dlm01VM65b\nKBnM5p50I9QEY4F4gSZfLLMNowKBgQC3+Tpz23qhkxJPu9cVuvPRFfg1y0NNsp7k\n0qHYMegz1GHoMVs1kg36OtbROqpG2+rIrj0JWTGwLO14fX2TY0jWfao21CGrpkXM\nlY1KSDIMuQv6pd5yh74oqvDDpZwKxxu/nmzcQbd1lN/nFkEW5g0b7e27UoCoovwS\n7qSENbqOLwKBgHYWp8H+aX1stPQZ8p1DngiupTE2FK5yIz/Y4T0JuFBNE+nmdOGL\n2sCFoUXC5sHLwjlNXBAHbCCV66akk/th5yvPR2NNIOWk51bMnOo+Q3GQEJJhRPLO\nhhzhZlN5+IPhzYmtU3jbdjsTzEex3J6GR64b3fqRu4bttZJsmp2jopUnAoGAWW+7\nzt7/+tR4rnJu2Y2NQjQf+mbaTUddb2kWbPe2Hpw9DJgR8zURvngkPor6hIAc33p1\nCbpmwXLV7yFyjthRbJSizwzJYZzvicmaamY2jqDXBf7k6WC8PSD88t/rwAGTp8/o\ntBruiSbawoi7E9q6vL0qOUqeaVzylnGVYQCNtNkCgYBwqL1MNTR8IrXDfZyYdDRP\nWNCRqm7ymuQi7IUKVa+yaBM1ubvEe7EPrlZWlFPDdPmaScx02Qwf++xcGHWzzDX0\nQPmd95OTGafvECXuKVy2NAf2AdCYVruL+17wfPhuz7ANIpgEqsiNAZNe0GtGBjyZ\nVuiSVVML3jW4XUtf63V0/A==\n-----END PRIVATE KEY-----\n";
$private_key = openssl_get_privatekey($raw_key_data);
if ($private_key === FALSE)
{
error_log ("Unable to extract private key from raw key data: " . openssl_error_string());
};
print("$private_key\n");
//{Base64url encoded JSON header}
$jwtHeader = base64url_encode(json_encode(array(
"alg" => "RS256",
"typ" => "JWT"
)));
//{Base64url encoded JSON claim set}
$now = time();
$jwtClaim = base64url_encode(json_encode(array(
"iss" => "myService-account-id#mySite.iam.gserviceaccount.com",
"scope" => "https://www.googleapis.com/auth/drive.file",
"aud" => "https://www.googleapis.com/oauth2/v4/token",
"exp" => $now + 3600,
"iat" => $now
)));
//The base string for the signature: {Base64url encoded JSON header}. {Base64url encoded JSON claim set}
openssl_sign(
$jwtHeader.".".$jwtClaim,
$jwtSig,
$private_key,
"SHA256"
);
$jwtSign = base64url_encode($jwtSig);
//{Base64url encoded JSON header}.{Base64url encoded JSON claim set}. {Base64url encoded signature}
$jwtAssertion = $jwtHeader.".".$jwtClaim.".".$jwtSig;
print("$jwtAssertion\n");
?>
When I run this code, I get Resource id #4 followed by a nonsense signature. I even tried putting the private key in a file and reading from the file in which there is multiple lines (private key separated at \n ) but no success. I can't generate meaningful JWT signature from the private key. Can anyone help me with this issue?
Thanks,
I have managed to solve this issue. Below is the updated code that reads the JSON file downloaded from Google and creates an assertion with which one can then get access token from Google:
<?php
//helper function
function base64url_encode($data) {
return rtrim(strtr(base64_encode($data), '+/', '-_'), '=');
}
// Read the JSON credential file my-private-key.json download from Google
$private_key_file="my-private-key.json";
$json_file = file_get_contents($private_key_file);
$info = json_decode($json_file);
$private_key = $info->{'private_key'};
//{Base64url encoded JSON header}
$jwtHeader = base64url_encode(json_encode(array(
"alg" => "RS256",
"typ" => "JWT"
)));
//{Base64url encoded JSON claim set}
$now = time();
$jwtClaim = base64url_encode(json_encode(array(
"iss" => $info->{'client_email'},
"scope" => "https://www.googleapis.com/auth/drive.file",
"aud" => "https://www.googleapis.com/oauth2/v4/token",
"exp" => $now + 3600,
"iat" => $now
)));
$data = $jwtHeader.".".$jwtClaim;
// Signature
$Sig = '';
openssl_sign($data,$Sig,$private_key,'SHA256');
$jwtSign = base64url_encode( $Sig );
//{Base64url encoded JSON header}.{Base64url encoded JSON claim set}.{Base64url encoded signature}
$jwtAssertion = $data.".".$jwtSign;
echo "$jwtAssertion\n";
You may then test your code by communicating with Google Drive for example:
curl -d 'grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer&assertion=YOUR-JWT-ASSERTION-BUILD-ABOVE' https://www.googleapis.com/oauth2/v4/token