PHP Authorization with JWT (JSON Web Tokens)
There was a time when the only way to authenticate yourself with an application was by providing your credentials (usually a username or email address and a password) and a session was then used to maintain user state until the user logged out. A little while later, we started using authentication APIs. And in yet more recent times, JWTs, or JSON Web Tokens, have been increasingly used as another way to authenticate requests to a server.
In this article, you’ll learn what JWTs are and how to use them with PHP to make authenticated user requests.
JWTs versus Sessions
But first, why are sessions not such a good thing? Well, there are three key reasons:
- Data is stored in plain text on the server.
Even though the data is usually not stored in a public folder, anyone with sufficient access to the server can read the contents of session files. - They involve filesystem read/write requests.
Every time a session starts or its data is modified, the server needs to update the session file. The same goes for every time the application sends a session cookie. If you have a large number of users, you can end up with a slow server unless you use alternative session storage options, such as Memcached and Redis. - Distributed/Clustered applications.
Since session files are, by default, stored on the file system, it’s hard to have a distributed or clustered infrastructure for high availability applications — ones that require the use of technologies such as load balancers and clustered servers. Other storage media and special configurations have to be implemented — and be done so in full awareness of their implications.
JWT
Now, let’s start learning about JWTs. The JSON Web Token specification (RFC 7519) was first published on December 28, 2010, and was most recently updated in May 2015.
JWTs have many advantages over API keys, including:
- API keys are random strings, whereas JWTs contain information and metadata. This information and metadata can describe a wide range of things, such as a user’s identity, authorization data, and the validity of the token within a time frame or in relation to a domain.
- JWTs don’t require a centralized issuing or revoking authority.
- JWTs are OAUTH2 compatible.
- JWT data can be inspected.
- JWTs have expiration controls.
- JWTs are intended for space-constrained environments, such as HTTP Authorization headers.
- Data is transmitted in JavaScript Object Notation format (JSON).
- JWTs are represented using Base64url encoding
What Does a JWT Look Like?
Here is a sample JWT:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE0MTY5MjkxMDksImp0aSI6ImFhN2Y4ZDBhOTVjIiwic2NvcGVzIjpbInJlcG8iLCJwdWJsaWNfcmVwbyJdfQ.XCEwpBGvOLma4TCoh36FU7XhUbcskygS81HE1uHLf0E
At first glance, it appears that the string is just random groups of characters concatenated with a period or dot character. As such, it may not seem very different from an API key. However, if you look more closely, there are three separate strings.
The JWT Header
The first string is the JWT header. It’s a Base64, URL-encoded JSON string. It specifies which cryptographic algorithm was used to generate the signature, and the token’s type, which is always set to JWT
. The algorithm can be either symmetric or asymmetric.
A symmetric algorithm uses a single key to both create and verify the token. The key is shared between the creator of the JWT and the consumer of it. It’s essential that you make sure only the creator and consumer knows the secret. Otherwise, anyone can create a valid token.
An asymmetric algorithm uses a private key to sign the token and a public key to verify it. These algorithms should be used when a shared secret is impractical or other parties only need to verify the integrity of the token.
The JWT’s Payload
The second string is the JWT’s payload. It’s also a Base64, URL-encoded JSON string. It contains some standard fields, which are referred to as “claims”. There are three types of claims: registered, public, and private.
Registered claims are predefined. You can find a list of them in the JWT’s RFC. Here are some commonly used ones:
iat
: the timestamp of token issuing.key
: a unique string, which could be used to validate a token, but goes against not having a centralized issuer authority.iss
: a string containing the name or identifier of the issuer. Can be a domain name and can be used to discard tokens from other applications.nbf
: a timestamp of when the token should start being considered valid. Should be equal to or greater thaniat
.exp
: a timestamp of when the token should cease to be valid. Should be greater thaniat
andnbf
.
Public claims can be defined as you see fit. However, they can’t be the same as registered claims, or claims of already existing public claims. You can create private claims at will. They’re only for use between two parties: a producer and a consumer.
The JWT’s Signature
The JWT’s signature is a cryptographic mechanism designed to secure the JWT’s data with a digital signature unique to the contents of the token. The signature ensures the JWT’s integrity so that consumers can verify it hasn’t been tampered with by a malicious actor.
The JWT’s signature is a combination of three things:
- the JWT’s header
- the JWT’s payload
- a secret value
These three are digitally signed (not encrypted) using the algorithm specified in the JWT’s header. If we decode the example above, we’ll have the following JSON strings:
The JWT’s Header
{
"alg": "HS256",
"typ": "JWT"
}
The JWT’s Data
{
"iat": 1416929109,
"jti": "aa7f8d0a95c",
"scopes": [
"repo",
"public_repo"
]
}
Try out jwt.io for yourself, where you can play around with encoding and decoding your own JWTs.
Let’s Use JWTs in a PHP-based Application
Now that you’ve learned what JWTs are, it’s now time to learn how to use them in a PHP app. Before we dive in, feel free to clone the code for this article, or follow along and create it as we go.
There are many ways that you can approach integrating JWTs, but here’s how we’re going to do it.
All requests to the application, except for the login and logout page, need to be authenticated via a JWT. If a user makes a request without a JWT, they’ll be redirected to the login page.
After a user fills out and submits the login form, the form will be submitted via JavaScript to the login endpoint, authenticate.php
, in our application. The endpoint will then extract the credentials (a username and password) from the request and check if they’re valid.
If they are, it will generate a JWT and send it back to the client. When the client receives a JWT, it will store it and use it with every future request to the application.
For a simplistic scenario, there’ll only be one resource the user can request — a PHP file aptly named resource.php
. It won’t do much, just returning a string, containing the current timestamp at the time of the request.
There’s couple of ways to use JWTs when making requests. In our application, the JWT will be sent in the Bearer authorization header.
If you’re not familiar with Bearer Authorization, it’s a form of HTTP authentication, where a token (such as a JWT) is sent in a request header. The server can inspect the token and determine if access should be given to the “bearer” of the token.
Here’s an example of the header:
Authorization: Bearer ab0dde18155a43ee83edba4a4542b973
For each request received by our application, PHP will attempt to extract the token from the Bearer header. If it’s present, it’s then validated. If it’s valid, the user will see the normal response for that request. If the JWT is invalid, however, the user won’t be allowed to access the resource.
Please note that JWT was not designed to substitute session cookies.
Prerequisites
To begin with, we need to have PHP and Composer installed on our systems.
In the project’s root, run composer install
. This will pull in Firebase PHP-JWT, a third-party library that simplifies working with JWTs, as well as laminas-config, designed to simplify access to configuration data within applications
The Login Form
With the library installed, let’s step through the login code in authenticate.php
. We first do the usual setup, ensuring that the Composer-generated autoloader is available.
<?php
declare(strict_types=1);
use Firebase\JWT\JWT;
require_once('../vendor/autoload.php');
After receiving the form submission, the credentials are validated against a database, or some other data store. For the purposes of this example, we’ll assume that they’re valid, and set $hasValidCredentials
to true.
<?php
// extract credentials from the request
if ($hasValidCredentials) {
Next, we initialize a set of variables to be used for generating the JWT. Please bear in mind that since a JWT can be inspected client-side, do not include any sensitive information in it.
Another thing worth pointing out, again, is that $secretKey
wouldn’t be initialized like this. You’d likely set it in the environment and extract it, using library such as phpdotenv, or in a config file. I’ve avoided doing that in this example, as I want to focus on the JWT code.
Never disclose it or store it under version control!
$secretKey = 'bGS6lzFqvvSQ8ALbOxatm7/Vk7mLQyzqaS34Q4oR1ew=';
$issuedAt = new DateTimeImmutable();
$expire = $issuedAt->modify('+6 minutes')->getTimestamp(); // Add 60 seconds
$serverName = "your.domain.name";
$username = "username"; // Retrieved from filtered POST data
$data = [
'iat' => $issuedAt->getTimestamp(), // Issued at: time when the token was generated
'iss' => $serverName, // Issuer
'nbf' => $issuedAt->getTimestamp(), // Not before
'exp' => $expire, // Expire
'userName' => $username, // User name
];
With the payload data ready to go, we next use php-jwt’s static encode
method to create the JWT.
The method:
- transforms the array to JSON
- produce the headers
- signs the payload
- encodes the final string
It takes three parameters:
- the payload information
- the secret key
- the algorithm to use to sign the token
By calling echo
on the result of the function, the generated token is returned:
<?php
// Encode the array to a JWT string.
echo JWT::encode(
$data,
$secretKey,
'HS512'
);
}
Consuming the JWT
Now that the client has the token, you can store it using JavaScript or whichever mechanism you prefer. Here’s an example of how to do so using vanilla JavaScript. In index.html
, after a successful form submission, the returned JWT is stored in memory, the login form is hidden, and the button to request the timestamp is displayed:
const store = {};
const loginButton = document.querySelector('#frmLogin');
const btnGetResource = document.querySelector('#btnGetResource');
const form = document.forms[0];
// Inserts the jwt to the store object
store.setJWT = function (data) {
this.JWT = data;
};
loginButton.addEventListener('submit', async (e) => {
e.preventDefault();
const res = await fetch('/authenticate.php', {
method: 'POST',
headers: {
'Content-type': 'application/x-www-form-urlencoded; charset=UTF-8'
},
body: JSON.stringify({
username: form.inputEmail.value,
password: form.inputPassword.value
})
});
if (res.status >= 200 && res.status <= 299) {
const jwt = await res.text();
store.setJWT(jwt);
frmLogin.style.display = 'none';
btnGetResource.style.display = 'block';
} else {
// Handle errors
console.log(res.status, res.statusText);
}
});
Using the JWT
When clicking on the “Get current timestamp” button, a GET request is made to resource.php
, which sets the JWT received after authentication in the Authorization header.
btnGetResource.addEventListener('click', async (e) => {
const res = await fetch('/resource.php', {
headers: {
'Authorization': `Bearer ${store.JWT}`
}
});
const timeStamp = await res.text();
console.log(timeStamp);
});
When we click the button, a request similar to the following is made:
GET /resource.php HTTP/1.1
Host: yourhost.com
Connection: keep-alive
Accept: */*
X-Requested-With: XMLHttpRequest
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE0MjU1ODg4MjEsImp0aSI6IjU0ZjhjMjU1NWQyMjMiLCJpc3MiOiJzcC1qd3Qtc2ltcGxlLXRlY25vbTFrMy5jOS5pbyIsIm5iZiI6MTQyNTU4ODgyMSwiZXhwIjoxNDI1NTkyNDIxLCJkYXRhIjp7InVzZXJJZCI6IjEiLCJ1c2VyTmFtZSI6ImFkbWluIn19.HVYBe9xvPD8qt0wh7rXI8bmRJsQavJ8Qs29yfVbY-A0
Assuming that the JWT is valid, we’d see the resource, after which the response is written to the console.
Validating the JWT
Finally, let’s look at how we can validate the token in PHP. As always, we’d include Composer’s autoloader. We could then, optionally, check if the correct request method’s been used. I’ve skipped over the code to do that, to continue focusing on the JWT-specific code:
<?php
chdir(dirname(__DIR__));
require_once('../vendor/autoload.php');
// Do some checking for the request method here, if desired.
Then, the code would attempt to extract the token from the Bearer header. I’ve done so using preg_match. If you’re not familiar with the function, it performs a regular expression match on a string
The regular expression that I’ve used here will attempt to extract the token from the Bearer header, and dump everything else. If it’s not found, an HTTP 400 Bad Request is returned:
if (! preg_match('/Bearer\s(\S+)/', $_SERVER['HTTP_AUTHORIZATION'], $matches)) {
header('HTTP/1.0 400 Bad Request');
echo 'Token not found in request';
exit;
}
Note that, by default, Apache will not pass the HTTP_AUTHORIZATION
header to PHP. The reason behind this is:
The basic authorization header is only secure if your connection is done over HTTPS, since otherwise the credentials are sent in encoded plain text (not encrypted) over the network which is a huge security issue.
I fully appreciate the logic of this decision. However, to avoid a lot of confusion, add the following to your Apache configuration. Then the code will function as expected. If you’re using NGINX, the code should function as expected:
RewriteEngine On
RewriteCond %{HTTP:Authorization} ^(.+)$
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
Next, we attempt to extract the matched JWT, which would be in the second element of the $matches
variable. If it’s not available, then no JWT was extracted, and an HTTP 400 Bad Request is returned:
$jwt = $matches[1];
if (! $jwt) {
// No token was able to be extracted from the authorization header
header('HTTP/1.0 400 Bad Request');
exit;
}
If we get to this point, a JWT was extracted, so we move to the decoding and validation stage. To do that, we need our secret key again, which would be pulled from the environment or the application’s configuration. We then use php-jwt’s static decode
method, passing to it the JWT, the secret key, and an array of algorithms to use to decode the JWT.
If it’s able to be successfully decoded, we then attempt to validate it. The example I have here is quite simplistic, as it only uses the issuer, not before and expiry timestamps. In a real application, you’d likely use a number of other claims as well.
$secretKey = 'bGS6lzFqvvSQ8ALbOxatm7/Vk7mLQyzqaS34Q4oR1ew=';
$token = JWT::decode($jwt, $secretKey, ['HS512']);
$now = new DateTimeImmutable();
$serverName = "your.domain.name";
if ($token->iss !== $serverName ||
$token->nbf > $now->getTimestamp() ||
$token->exp < $now->getTimestamp())
{
header('HTTP/1.1 401 Unauthorized');
exit;
}
If the token isn’t valid because, for example, the token has expired, the user will be sent an HTTP 401 Unauthorized header, and the script will exit.
If the process to decode the JWT fails, it could be that:
- The number of segments provided did not match the standard three as described earlier.
- The header or the payload is not a valid JSON string
- The signature is invalid, which means the data was tampered with!
- The
nbf
claim is set in the JWT with a timestamp when the current timestamp is less than that. - The
iat
claim is set in the JWT with a timestamp when the current timestamp is less than that. - The
exp
claim is set in the JWT with a timestamp when the current timestamp is more than that.
As you can see, JWT has a nice set of controls that will mark it as invalid, without the need to manually revoke it or check it against a list of valid tokens.
If the decode and validation process succeeds, the user will be allowed to make the request, and will be sent the appropriate response.
In Conclusion
That’s a quick introduction to JSON Web Tokens, or JWTs, and how to use them in PHP-based applications. From here on, you can try to implement JWTs in your next API, maybe trying some other signing algorithms that use asymmetric keys like RS256, or integrating it in an existing OAUTH2 authentication server to be the API key.
If you have any comments or questions, feel free to get in touch on Twitter.
FAQs About PHP Authorization with JWT
You can indeed utilize JWT in PHP to establish authentication and authorization mechanisms within your web applications. To get started, you’ll need to install a PHP JWT library such as “firebase/php-jwt” or “lcobucci/jwt” using Composer. These libraries provide the necessary tools for creating, encoding, decoding, and verifying JWTs.
To create a JWT, you can use the library to build a token with claims such as issuer, audience, expiration time, and more. Once created, you sign the token with your secret key. When receiving a JWT, you can decode and verify it using the library to ensure its authenticity. If the token is valid and verified, you can access its claims to determine user identity and permissions, allowing you to implement secure authentication and authorization in your PHP application.
It’s crucial to safeguard your secret key and follow security best practices when working with JWTs to prevent unauthorized access to your application’s resources.
JWT (JSON Web Token) authentication in PHP is a method widely used to implement user authentication and authorization in web applications. It operates on the basis of token-based authentication, enabling secure and stateless user verification. Here’s how JWT authentication functions in PHP:
Firstly, during user authentication or login, the server generates a JWT, a compact, self-contained token that includes user-related information (claims) like user ID, username, and roles. These claims are typically JSON data. The server then signs this token with a secret key to ensure its integrity and authenticity.
Secondly, upon successful authentication, the server sends the JWT back to the client, which commonly stores it in secure locations like HTTP cookies or local storage. This token serves as the proof of authentication.
Finally, for subsequent requests to protected resources on the server, the client attaches the JWT in the request header, typically using the “Authorization” header with the “Bearer” scheme. The server, upon receiving the JWT, verifies its signature using the shared secret key. If the signature is valid, it proceeds to decode and validate the claims within the token, ensuring that it hasn’t expired and that the user has the necessary permissions for the requested resource. This approach allows for secure, stateless authentication in PHP applications without the need for server-side session storage.
While JWT authentication in PHP offers numerous benefits, such as scalability and statelessness, it’s essential to safeguard the secret key and adopt best practices for token management to maintain the security of your application. Utilizing established PHP JWT libraries can streamline token handling and enhance security.
An alternative to JWT (JSON Web Tokens) for authentication and authorization in PHP is session-based authentication. In session-based authentication, the server maintains a session for each authenticated user. When a user logs in, a unique session identifier, often stored as a session cookie on the client’s browser, is created. This identifier is used to associate the user with server-side session data, including user-related information such as user ID, username, and permissions.
Session-based authentication offers simplicity and ease of implementation, making it suitable for various web applications, especially when you don’t require the statelessness and scalability features of JWTs. It is inherently stateful, which can be advantageous when you need to manage user-specific data throughout a user’s session, such as shopping cart contents or user preferences.
However, there are some considerations to keep in mind when using session-based authentication. It may be less suitable for distributed or microservices-based architectures where stateless authentication is preferred. Additionally, managing user sessions can introduce server load, especially in applications with a large user base. Ultimately, the choice between JWT and session-based authentication in PHP should align with your application’s specific requirements and design considerations, ensuring a secure and efficient authentication mechanism that best suits your needs.
Securing a PHP API using JWT (JSON Web Tokens) involves a multi-step process that combines authentication and authorization. You begin by selecting a suitable PHP JWT library like “firebase/php-jwt” or “lcobucci/jwt” to handle token-related operations and manage dependencies using Composer.
For authentication, you need to implement a user authentication system within your PHP application. This system verifies user credentials against your database or an authentication provider. Upon successful authentication, you generate a JWT token containing user-related claims such as the user’s ID, username, and role. It’s crucial to set an expiration time to control the token’s validity and then sign the token using a secret key. This signed token is sent back to the client as part of the authentication response.
Clients store the received JWT securely, typically in HTTP cookies or local storage. For subsequent API requests, clients include the JWT in the request header as an “Authorization” header with the “Bearer” scheme. In your PHP API, you validate incoming JWTs by verifying their signature using the same secret key used during token creation. Additionally, you check that the token hasn’t expired and contains valid claims. After successful validation, you extract user information from the token claims and implement authorization logic to ensure users have the required permissions to access the requested resource.
Keeping your secret key secure is of paramount importance, as it’s essential for both signing and verifying tokens. Any compromise of this key could lead to significant security vulnerabilities.