namespace Automattic\WooCommerce\StoreApi\Utilities;
* Simple Json Web Token generator & verifier static utility class, currently supporting only HS256 signatures.
final class JsonWebToken {
private static $type = 'JWT';
* JWT algorithm to generate signature.
private static $algorithm = 'HS256';
* Generates a token from provided data and secret.
* @param array $payload Payload data.
* @param string $secret The secret used to generate the signature.
public static function create( array $payload, string $secret ) {
$header = self::to_base_64_url( self::generate_header() );
$payload = self::to_base_64_url( self::generate_payload( $payload ) );
$signature = self::to_base_64_url( self::generate_signature( $header . '.' . $payload, $secret ) );
return $header . '.' . $payload . '.' . $signature;
* Validates a provided token against the provided secret.
* Checks for format, valid header for our class, expiration claim validity and signature.
* https://datatracker.ietf.org/doc/html/rfc7519#section-7.2
* @param string $token Full token string.
* @param string $secret The secret used to generate the signature.
public static function validate( string $token, string $secret ) {
if ( ! self::shallow_validate( $token ) ) {
$parts = self::get_parts( $token );
* Check if the token is based on our secret.
$encoded_regenerated_signature = self::to_base_64_url(
self::generate_signature( $parts->header_encoded . '.' . $parts->payload_encoded, $secret )
return hash_equals( $encoded_regenerated_signature, $parts->signature_encoded );
* Shallow validate a token, it does not check the signature or expiration, but it checks the structure and expiry.
* @param string $token Full token string.
public static function shallow_validate( string $token ) {
* Confirm the structure of a JSON Web Token, it has three parts separated
* by dots and complies with Base64URL standards.
if ( preg_match( '/^[a-zA-Z\d\-_=]+\.[a-zA-Z\d\-_=]+\.[a-zA-Z\d\-_=]+$/', $token ) !== 1 ) {
$parts = self::get_parts( $token );
* Check if header declares a supported JWT by this class.
! is_object( $parts->header ) ||
! property_exists( $parts->header, 'typ' ) ||
! property_exists( $parts->header, 'alg' ) ||
self::$type !== $parts->header->typ ||
self::$algorithm !== $parts->header->alg
* Check if token is expired.
if ( ! property_exists( $parts->payload, 'exp' ) || time() > (int) $parts->payload->exp ) {
* Returns the decoded/encoded header, payload and signature from a token string.
* @param string $token Full token string.
public static function get_parts( string $token ) {
$parts = explode( '.', $token );
'header' => json_decode( self::from_base_64_url( $parts[0] ) ),
'header_encoded' => $parts[0],
'payload' => json_decode( self::from_base_64_url( $parts[1] ) ),
'payload_encoded' => $parts[1],
'signature' => self::from_base_64_url( $parts[2] ),
'signature_encoded' => $parts[2],
* Generates the json formatted header for our HS256 JWT token.
private static function generate_header() {
'alg' => self::$algorithm,
* Generates a sha256 signature for the provided string using the provided secret.
* @param string $string Header + Payload token substring.
* @param string $secret The secret used to generate the signature.
private static function generate_signature( string $string, string $secret ) {
* Generates the payload in json formatted string.
* @param array $payload Payload data.
private static function generate_payload( array $payload ) {
return wp_json_encode( array_merge( $payload, [ 'iat' => time() ] ) );
* Encodes a string to url safe base64.
* @param string $string The string to be encoded.
private static function to_base_64_url( string $string ) {
base64_encode( $string ) // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
* Decodes a string encoded using url safe base64, supporting auto padding.
* @param string $string the string to be decoded.
private static function from_base_64_url( string $string ) {
* Add padding to base64 strings which require it. Some base64 URL strings
* which are decoded will have missing padding which is represented by the
if ( strlen( $string ) % 4 !== 0 ) {
return self::from_base_64_url( $string . '=' );
return base64_decode( // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode