A simple PHP library for creating JSON Web Tokens that uses HMAC SHA256 to sign signatures. For basic usage the library exposes a static interface to allow developers to create a token that stores a user identifier and expiration time.
The library is also open to extension, developers can define their own encoding standard, their own secret validation, set all the RFC standard JWT claims, and set their own private claims.
You can easily integrate ReallySimpleJWT with PSR-7 / PSR-15 compliant frameworks such as Slim PHP with the PSR-JWT middleware library. Please read the framework integration documentation to learn more.
If you need to read tokens in the browser please take a look at our JavaScript / Typescript library RS-JWT.
- What is a JSON Web Token?
- Setup
- Basic Usage
- Advanced Usage
- Error Messages and Codes
- Token Security
- Framework Integration With PSR-JWT Middleware
- Browser Integration With RS-JWT
JSON Web Tokens is a standard for creating URL friendly access tokens that assert claims about a user or system.
A token is broken down into three parts; the header, the payload and the signature; with each part separated by a dot. Each part is encoded using the base64URL standard, see the RFC.
An example JWT:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
The header and payload are both encoded JSON strings that contain a number of claims:
// Example Header
{
"alg": "HS256",
"typ": "JWT"
}
// Example Payload
{
"sub": "1234567890",
"name": "John Doe",
"iat": 1516239022
}
A claim is a key value pair, eg "typ": "JWT"
, please read RFC 7519 to learn more about JSON Web Token claims.
Token security is achieved via the signature which is made up of the header, payload and a secret known only to the token author. This information is hashed and then base64URL encoded.
If a malicious user attempts to edit the header or payload claims they will be unable to replicate the signature so long as you use a strong secret. See Token Security for more information on this.
To install this package you will need to install Composer and then run composer init
. Once this is done you can install the package via the command line or by editing the composer.json file created by the composer init
command.
Finally you will need to reference the composer autoloader in your PHP code, require 'vendor/autoload.php';
. The location of the autoload file will differ dependent on where your code is run. Also you will not need to reference the autoload file if you are using a framework like Laravel or Symfony.
Install via Composer on the command line:
composer require rbdwllr/reallysimplejwt
Install via the composer.json file:
Add the following to your composer.json file:
"require": {
"rbdwllr/reallysimplejwt": "^4.0"
}
Then run:
composer update
For basic usage the library exposes a set of static methods via the ReallySimpleJWT\Token
class which allow a developer to create and validate basic JSON Web Tokens.
Call the create()
static method and pass in a user identifier, a secret, an expiration date time number and the token issuer.
This will return a token string on success and throw a ReallySimpleJWT\Exception\BuildException
on failure.
use ReallySimpleJWT\Token;
$userId = 12;
$secret = 'sec!ReT423*&';
$expiration = time() + 3600;
$issuer = 'localhost';
$token = Token::create($userId, $secret, $expiration, $issuer);
To create a more customised token developers can use the customPayload()
method. This allows the creation of a token based on an array of key value pairs which represent the payload claims.
use ReallySimpleJWT\Token;
$payload = [
'iat' => time(),
'uid' => 1,
'exp' => time() + 10,
'iss' => 'localhost'
];
$secret = 'Hello&MikeFooBar123';
$token = Token::customPayload($payload, $secret);
On success the customPayload()
method will return a JWT token string and on failure it will throw an exception.
To validate a JSON web token call the validate()
static method, pass in the token string and the secret. The validate method checks the token structure is correct and the signature is valid.
It will return true on success and false on failure.
use ReallySimpleJWT\Token;
$token = 'aaa.bbb.ccc';
$secret = 'sec!ReT423*&';
$result = Token::validate($token, $secret);
There are also methods available to validate the token's expiration claim and not before claim. Both will return true on success and false on failure.
Token::validateExpiration($token, $secret);
Token::validateNotBefore($token, $secret);
To retrieve the token claims data from the header or payload call the getHeader()
and or getPayload()
static methods.
Both methods will return an associative array on success and throw an exception on failure.
use ReallySimpleJWT\Token;
$token = 'aaa.bbb.ccc';
$secret = 'sec!ReT423*&';
// Return the header claims
Token::getHeader($token, $secret);
// Return the payload claims
Token::getPayload($token, $secret);
The ReallySimpleJWT\Token
class also provides three factory methods to gain access to the core ReallySimpleJWT\Build
, ReallySimpleJWT\Parse
, and ReallySimpleJWT\Validate
classes. These classes allow you to build custom tokens, and parse and validate tokens as you see fit.
Token::builder(); // Returns an instance of ReallySimpleJWT\Build
Token::parser($token, $secret); // Returns an instance of ReallySimpleJWT\Parse
Token::validator($token, $secret); // Returns an instance of ReallySimpleJWT\Validate
The ReallySimpleJWT\Token
class is also just a wrapper of the ReallySimpleJWT\Tokens
class which can be used directly for those who'd prefer to instantiate and inject the functionality.
use ReallySimpleJWT\Tokens;
$tokens = new Tokens();
$id = 52;
$secret = 'sec!ReT423*&';
$expiration = time() + 50;
$issuer = 'localhost';
$token = $tokens->create('id', $id, $secret, $expiration, $issuer);
$token->getToken();
Please note when calling the create()
and customPayload()
methods on the Tokens
class they will return an instance of the Jwt
class unlike the Token
class which will return a token string.
In addition, the create()
method has a slightly different signature on the Tokens
class as a user identifier key must be passed in.
create(string $userKey, $userId, string $secret, int $expiration, string $issuer): Jwt
To create customised JSON Web Tokens developers need to access the ReallySimpleJWT\Build
, ReallySimpleJWT\Parse
and ReallySimpleJWT\Validate
classes directly.
The ReallySimpleJWT\Build
class allows you to create a completely unique JSON Web Token. It has helper methods for all the RFC defined header and payload claims. For example, the setIssuer()
method will add the iss
claim to the token payload.
The class also allows developers to set custom header and payload claims via the setHeaderClaim()
and setPayloadClaim()
methods.
The methods can be chained together and when the build()
method is called the token will be generated and returned as a ReallySimpleJWT\Jwt
object.
use ReallySimpleJWT\Build;
use ReallySimpleJWT\Secret;
use ReallySimpleJWT\Helper\Validator;
use ReallySimpleJWT\Encoders\EncodeHS256;
$build = new Build('JWT', new Validator(), new Secret(), new EncodeHS256());
$token = $build->setContentType('JWT')
->setHeaderClaim('info', 'foo')
->setSecret('!secReT$123*')
->setIssuer('localhost')
->setSubject('admins')
->setAudience('https://google.com')
->setExpiration(time() + 30)
->setNotBefore(time() - 30)
->setIssuedAt(time())
->setJwtId('123ABC')
->setPayloadClaim('uid', 12)
->build();
A ReallySimpleJWT\Jwt
object is returned when a developer calls the build()
method on the ReallySimpleJWT\Build
class. The Jwt class offers two methods getToken()
and getSecret()
. The former returns the generated JSON Web Token and the latter returns the secret used for the token signature.
To parse a JSON Web Token via the ReallySimpleJWT\Parse
class a developer must first create a new ReallySimpleJWT\Jwt
object by injecting the token and secret.
use ReallySimpleJWT\Jwt;
$token = 'aaa.bbb.ccc';
$secret = '!secReT$123*';
$jwt = new Jwt($token, $secret);
// Return the token
$jwt->getToken();
// Return the secret
$jwt->getSecret();
The ReallySimpleJWT\Parse
class allows a developer to parse a JWT and the parse()
method will decode the JSON Web Token and return the result as a ReallySimpleJWT\Parsed
object. This will provide access to the header and payload claims data the token holds.
use ReallySimpleJWT\Parse;
use ReallySimpleJWT\Jwt;
use ReallySimpleJWT\Decode;
$token = 'aaa.bbb.ccc';
$secret = '!secReT$123*';
$jwt = new Jwt($token, $secret);
$parse = new Parse($jwt, new Decode());
$parsed = $parse->parse();
// Return the token header claims as an associative array.
$parsed->getHeader();
// Return the token payload claims as an associative array.
$parsed->getPayload();
The ReallySimpleJWT\Parsed
class is returned when a developer calls the parse()
method on the ReallySimpleJWT\Parse
class.
It provides a number of helper methods to gain access to the token claim data. A developer can call the getHeader()
and getPayload()
methods to gain access to the respective claim data as associative arrays.
Alternatively a developer can call one of the RFC compliant claim methods:
Header
getAlgorithm()
getType()
getContentType()
Payload
getIssuer()
getSubject()
getAudience()
getExpiration()
getNotBefore()
getIssuedAt()
getJwtId()
getExpiresIn()
getUsableIn()
To Validate a JSON Web Token a developer can use the ReallySimpleJWT\Validate
class. To use the validate class you need to create and inject an instance of the ReallySimpleJWT\Parse
class. This is so the validate class can access the information contained within the token.
use ReallySimpleJWT\Jwt;
use ReallySimpleJWT\Parse;
use ReallySimpleJWT\Validate;
use ReallySimpleJwt\Decode;
use ReallySimpleJwt\Encoders\EncodeHS256;
use ReallySimpleJwt\Helper\Validator;
$token = new Jwt(
'abc.def.ghi',
'!$£%456hftYuJi2'
);
$parse = new Parse($token, new Decode());
$validate = new Validate(
$parse,
new EncodeHS256(),
new Validator()
);
$validate->structure();
$validate->signature();
Six validation methods are available which can all be chained:
structure()
confirms the structure of the token is correct.signature()
confirms the token signature is valid.expiration()
confirms the token expiration claim (exp
) has not expired.notBefore()
confirms the token not before claim (nbf
) has elapsed.audience()
confirms the token audience claim (aud
) matches what is expected.algorithm()
confirms the token algorithm claim (alg
) matches what is expected and is valid (See: RFC 7518).
Each validation method will throw a ReallySimpleJWT\Exception\ValidateException
if there is anything wrong with the supplied token.
By default this library hashes and encodes the JWT signature via hash_hmac()
using the sha256 algorithm. If a developer would like to use a customised form of encoding they just need to generate a custom encode class which complies with the ReallySimpleJWT\Interfaces\Encode
interface. This can then be injected into the ReallySimpleJWT\Build
and ReallySimpleJWT\Validate
classes.
interface EncodeInterface
{
public function getAlgorithm(): string;
public function encode(string $toEncode): string;
public function signature(string $header, string $payload, string $secret): string;
}
The ReallySimpleJWT library will in a number of situations throw exceptions to highlight problems when creating, parsing and validating JWT tokens. The error codes, messages and their explanations are in the table below.
There are four exception types that may be thrown:
ReallySimpleJWT\Exception\BuildException
ReallySimpleJWT\Exception\ParseException
ReallySimpleJWT\Exception\TokensException
ReallySimpleJWT\Exception\ValidateException
Code | Message | Explanation |
---|---|---|
1 | Token is invalid. | Token must have three parts separated by dots. |
2 | Audience claim does not contain provided StringOrURI. | The aud claim must contain the provided string or URI string provided. |
3 | Signature is invalid. | Signature does not match header / payload content. Could not replicate signature with provided header, payload and secret. |
4 | Expiration claim has expired. | The exp claim must be a valid date time number in the future. |
5 | Not Before claim has not elapsed. | The nbf claim must be a valid date time number in the past. |
6 | Expiration claim is not set. | Attempt was made to validate an exp claim which does not exist. |
7 | Not Before claim is not set. | Attempt was made to validate a nbf claim which does not exist. |
8 | Invalid payload claim. | Payload claims must be key value pairs of the format string: mixed . |
9 | Invalid secret. | Must be 12 characters in length, contain upper and lower case letters, a number, and a special character *&!@%^#$ |
10 | Invalid Audience claim. | The aud claim can either be a string or an array of strings nothing else. |
11 | Audience claim is not set. | Attempt was made to validate an aud claim which does not exist. |
12 | Algorithm claim is not valid. | Algorithm should be a valid Digital Signature or MAC Algorithm, or none. See RFC 7518. |
13 | Algorithm claim is not set. | Attempt was made to validate an alg claim which does not exist. |
The JWT RFC 7519 allows for the creation of tokens without signatures and without secured / hashed signatures. The ReallySimpleJWT library however imposes security by default as there is no logical reason not to. All created tokens must have a signature and a strong secret, but the library will validate tokens without a secret or a strong secret. The library will not validate tokens without a signature.
It is possible to edit and enhance the implementation of the signature and its security level by creating a custom encode class that implements the ReallySimpleJWT\Interfaces\Encode
interface, or a custom secret class which implements the ReallySimpleJWT\Interfaces\Secret
interface. See sections Custom Encoding and Custom Secrets
This JWT library imposes strict secret security as follows: the secret must be at least 12 characters in length; contain numbers; upper and lowercase letters; and one of the following special characters *&!@%^#$
.
// Bad Secret
secret123
// Good Secret
sec!ReT423*&
The reason for this is that there are lots of JWT Crackers available meaning weak secrets are easy to crack thus rendering the security JWT offers useless.
While we advise strongly against using weak secrets for JWT signatures we do accept there are systems on the 'internets' which for one reason or another impose weak secrets on developers.
You can setup custom secret validation by creating your own secret class which implements the ReallySimpleJWT\Interfaces\Secret
interface. You can then pass this custom secret class to the ReallySimpleJWT\Build
class.
use ReallySimpleJWT\Interfaces\Secret;
use ReallySimpleJWT\Build;
use ReallySimpleJWT\Helper\Validator;
use ReallySimpleJWT\Encoders\EncodeHs256;
class CustomSecret implements Secret
{
public function validate(string $secret): bool
{
// Please do not copy this code, it is an example of weak secret validation.
return (bool) preg_match('/[a-z]+/', $secret);
}
}
// Create JWT Builder with Custom Secret Class.
$build = new Build(
'JWT',
new Validator(),
new CustomSecret(),
new EncodeHs256()
);
You can easily integrate ReallySimpleJWT with PSR-7 / PSR-15 compliant frameworks such as Slim PHP and Laminas by using the PSR-JWT library.
For example integration with Slim PHP only requires a few lines of code:
// Can be added to any routes file in Slim, often index.php.
require '../../vendor/autoload.php';
$app->get('/jwt', function (Request $request, Response $response) {
$response->getBody()->write("JSON Web Token is Valid!");
return $response;
})->add(\PsrJwt\Factory\JwtAuth::middleware('Secret123!456$', 'jwt', 'Authentication Failed'));
Please read the PSR-JWT documentation to learn more about integration options for ReallySimpleJWT.
When you create JSON Web Tokens you may wish to read some of the information contained in the header and payload claims in the browser.
If you do, we have an NPM packages for that called RS-JWT.
Install:
npm install --save rs-jwt
Usage:
import { parseJwt } from 'rs-jwt'
const result = parseJwt('json.web.token')
// Return the header claims as an object.
const header = result.getHeader()
// Access the type claim.
console.log(header.typ)
// Return the payload claims as an object.
const payload = result.getPayload()
// Access the expiry claim.
console.log(payload.exp)
For more information see the project README or visit the NPM Page.
MIT
Rob Waller
Twitter: @RobDWaller