Cara menggunakan nowakowskir php jwt

JWT is short for JSON Web Token. It is used eg. instead of sessions to maintain a login in a browser that is talking to an API - since browser sessions are vulnerable to CSRF security issues. JWT is also less complicated than setting up an OAuth authentication mechanism.

The concept relies on two tokens:

  • AccessToken - a short-lived JWT (eg. 5 minutes)

This token is generated using \sizeg\jwt\Jwt::class It is not stored server side, and is sent on all subsequent API requests through the Authorization header How is the user identified then? Well, the JWT contents contain the user ID. We trust this value blindly.

  • RefreshToken - a long-lived, stored in database

This token is generated upon login only, and is stored in the table user_refresh_token. A user may have several RefreshToken in the database.

Scenarios

In our actionLogin() method two things happens, if the credentials are correct:

  • The JWT AccessToken is generated and sent back through JSON. It is not stored anywhere server-side, and contains the user ID (encoded).
  • The RefreshToken is generated and stored in the database. It's not sent back as JSON, but rather as a httpOnly cookie, restricted to the /auth/refresh-token path.

The JWT is stored in the browser's localStorage, and have to be sent on all requests from now on. The RefreshToken is in your cookies, but can't be read/accessed/tempered with through Javascript (since it is httpOnly).

Token expired:

After some time, the JWT will eventually expire. Your API have to return

	public function behaviors() {
    	$behaviors = parent::behaviors();

		$behaviors['authenticator'] = [
			'class' => \sizeg\jwt\JwtHttpBearerAuth::class,
			'except' => [
				'login',
				'refresh-token',
				'options',
			],
		];

		return $behaviors;
	}
1 in this case. In your app's HTTP client (eg. Axios), add an interceptor, which detects the 401 status, stores the failing request in a queue, and calls the /auth/refresh-token endpoint.

When called, this endpoint will receive the RefreshToken via the cookie. You then have to check in your table if this is a valid RefreshToken, who is the associated user ID, generate a new JWT and send it back as JSON.

Your HTTP client must take this new JWT, replace it in localStorage, and then cycle through the request queue and replay all failed requests.

My laptop got stolen:

If you set up an

	public function behaviors() {
    	$behaviors = parent::behaviors();

		$behaviors['authenticator'] = [
			'class' => \sizeg\jwt\JwtHttpBearerAuth::class,
			'except' => [
				'login',
				'refresh-token',
				'options',
			],
		];

		return $behaviors;
	}
4 endpoint, that returns all the current user's RefreshTokens, you can then display a table of all connected devices.

You can then allow the user to remove a row (i.e. DELETE a particular RefreshToken from the table). When the compromised token expires (after eg. 5 min) and the renewal is attempted, it will fail. This is why we want the JWT to be really short lived.

Why do we trust the JWT blindly?

This is by design the purpose of JWT. It is secure enough to be trustable. In big setups (eg. Google), the Authentication is handled by a separate authentication server. It's responsible for accepting a login/password in exchange for a token.

Later, in Gmail for example, no authentication is performed at all. Google reads your JWT and give you access to your email, provided your JWT is not dead. If it is, you're redirected to the authentication server.

This is why when Google authentication had a failure some time ago - some users were able to use Gmail without any problems, while others couldn't connect at all - JWT still valid versus an outdated JWT.

JSON Web Tokens (JWTs) allow you to implement stateless authentication (without the use of server-side sessions). JWTs are digitally signed with a secret key and can contain various information about the user: identity, role, permissions, etc in JSON format. This information is simply encoded and not encrypted. However, because of the digital signature, the payload cannot be modified without access to the secret key.

JWTs are a relatively hot topic as they are widely used (especially in single-page applications and REST APIs) but many developers do not understand them very well. In this post, I’ll discuss what JWTs are, what problems they solve, how they work, and how to use them securely. Then I’ll walk you through the process of creating and verifying JWTs from scratch with PHP (and without any external libraries). Finally, I’ll show you how to use Okta’s JWT library to handle validation of Okta JWTs automatically. Okta is an API service that allows you to create, edit, and securely store user accounts and user account data, and connect them with one or more applications. Register for a forever-free developer account, and when you’re done, come back to learn more about JWTs.

The Big Secret about User Authentication

In the dark old days of the Internet, there was session-based authentication. Users would log in and if the server accepted their credentials, it would create a session for them (in a file, in the database, or in an in-memory key-value datastore like Memcached or Redis). Then the server would send back a cookie containing a

data = base64urlEncode(header) + "." + base64urlEncode(payload)
hashedData = hash(data, secret)
signature = base64urlEncode(hashedData)
8. The user’s browser would provide the cookie with each subsequent request, and the server would know the user’s identity without constantly asking for a username and a password.

This approach has some drawbacks: for example, if you want to scale horizontally you would need a central storage system for the sessions, which is a single point of failure. However, let me tell you a big secret: sessions worked in the past, and they still work just fine for the majority of use cases. If all you have is a simple website, where users register, then log in, then click around and do some stuff, server-side sessions are perfect. All modern Web frameworks still operate this way by default. You can even have all the cryptographic benefits of JWTs with simple sessions if you’re interested in that.

However, JWTs make a lot of sense if you’re building API services that support machine-to-machine or client-server communication (like single-page applications, or mobile applications). They also make sense if more than two parties are involved in a request, or if you’re implementing single sign-on/federated login systems.

How JWTs Work

The authentication system must provide a login endpoint. Users send their credentials to the login system (which can be a third-party sign on). After a successful login, the server creates a JWT and sends it to the client. The client application must store this JWT and pass it with each subsequent API call. The server can use the JWT to verify that the API call is coming from an authorized user. The authentication system is able to verify the integrity of the JWT and its payload with the secret key only (without any calls to a database or network operations).

JWTs can be signed using a secret (with the HMAC algorithm) or a public/private key pair using RSA or ECDSA.

It is extremely important to understand that JWTs do not hide or obscure the data they hold. The payload is trivially encoded and not encrypted, and the user can read it (so do not store anything sensitive there). Only the signature is encrypted and can be used by the authentication server to verify that the information in the token has not been modified.

Store and Use JWTs Securely

The client application should store the JWT and send it with every request to the API. If the token is stolen, a malicious third party can impersonate the legitimate user for as long as the token is valid. Therefore, it’s crucial to take all possible measures to keep the token secure.

There are two standard ways to store the token: in the local/session storage of the browser, or in a cookie. Here are the main risks and considerations when deciding which option to choose:

Man in the middle attacks – you need to make sure that the application only works over https so it’s not possible to sniff the token by intercepting the traffic (e.g. in a public wi-fi network).

Cross-Site Scripting (injecting of JavaScript, XSS) attacks – the local/session storage is accessible through a JavaScript API which makes it vulnerable to XSS attacks (if a hacker can perform a successful XSS attack which allows them to run their own JavaScript inside the target’s browser when visiting your website, the local/session storage is compromised along with all tokens in it). It’s not always trivial to secure a site completely against XSS attacks, especially if the site is based on user-generated content. Therefore it’s usually preferable to store the token in a cookie.

Cross-Site Request Forgery (CSRF) attacks – setting a https-only flag for the cookie eliminates the risk of XSS attacks or man-in-the-middle attacks (because these cookies are not available to JavaScript, or over non-secure connections). You still need to handle the risk of CSRF though. There are different ways to do it – one particularly effective option is to use the

data = base64urlEncode(header) + "." + base64urlEncode(payload)
hashedData = hash(data, secret)
signature = base64urlEncode(hashedData)
9 cookie attribute. Most modern Web application frameworks also include some default way to deal with CSRF.

There is one last topic I’d like to discuss about JWTs security – how to revoke a user’s access (for example, a user notifies you that their token is compromised, so you want to force them to login again, or a user is banned from your website and you want to restrict their access immediately).

There is no easy answer because of the stateless nature of JWTs – they are self-sufficient and (theoretically) should include all necessary information about a user’s permissions without consulting external resources. This means that you cannot force them to expire, so you must keep their expiration time short (15 to 60 minutes usually, and use refresh tokens which are tracked on the server side and verified for validity before re-issuing an access token). If you absolutely must be able to kick users immediately, then you have to track each access token at the backend and verify it’s not blacklisted on every request – but this approach loses the main benefit of JWTs (stateless authentication) and you’re back to a solution that’s dangerously close to server-side sessions.

To summarize, here’s the secure way to handle JWTs:

  • Sign your tokens with a strong key, and keep their expiration times low.
  • Store them in https-only cookies.
  • Use the
    {
        "require": {
            "vlucas/phpdotenv": "^2.4"
        },
        "autoload": {
            "psr-4": {
                "Src\\": "src/"
            }
        }
    }
    
    0 cookie attribute if it doesn’t affect your application’s functionality.
  • Use your Web application framework’s default way of dealing with CSRF if
    {
        "require": {
            "vlucas/phpdotenv": "^2.4"
        },
        "autoload": {
            "psr-4": {
                "Src\\": "src/"
            }
        }
    }
    
    0 is not an option for you.
  • Build your own CSRF token and backend code to verify each form request if you’re unlucky enough to use a framework that doesn’t handle CSRF out of the box.
  • Always verify the signature on the server side before you trust any information in the JWT.

The Structure of a JWT

Let’s get down to the nitty-gritty details of handling JWTs now. The definition:

“A JSON Web Token (JWT) is a JSON object that is defined in RFC 7519 as a safe way to represent a set of information between two parties. The token is composed of a header, a payload, and a signature.”

So a JWT is just a string in this format:

header.payload.signature

The header component of the JWT contains information about how the JWT signature should be computed.

The payload component of the JWT is the information about the user that’s stored inside the JWT (also referred to as ‘claims’ of the JWT).

The signature is computed like this:

data = base64urlEncode(header) + "." + base64urlEncode(payload)
hashedData = hash(data, secret)
signature = base64urlEncode(hashedData)

The secret must only be known by the authentication server (and the application server that provides the API, if it’s different from the authentication server).

Create and Validate JWTs From Scratch with PHP

We’ll start a new PHP project by creating a

{
    "require": {
        "vlucas/phpdotenv": "^2.4"
    },
    "autoload": {
        "psr-4": {
            "Src\\": "src/"
        }
    }
}
2 directory and a simple
{
    "require": {
        "vlucas/phpdotenv": "^2.4"
    },
    "autoload": {
        "psr-4": {
            "Src\\": "src/"
        }
    }
}
3 file with just one dependency (for now): the DotEnv library which will allow us to keep our secret key in a
{
    "require": {
        "vlucas/phpdotenv": "^2.4"
    },
    "autoload": {
        "psr-4": {
            "Src\\": "src/"
        }
    }
}
4 file outside our code repository:

{
    "require": {
        "vlucas/phpdotenv": "^2.4"
    },
    "autoload": {
        "psr-4": {
            "Src\\": "src/"
        }
    }
}
3

{
    "require": {
        "vlucas/phpdotenv": "^2.4"
    },
    "autoload": {
        "psr-4": {
            "Src\\": "src/"
        }
    }
}

We’ve also configured a PSR-4 autoloader which will automatically look for PHP classes in the

{
    "require": {
        "vlucas/phpdotenv": "^2.4"
    },
    "autoload": {
        "psr-4": {
            "Src\\": "src/"
        }
    }
}
2 directory.

We can install our dependencies now:

composer install

We have a

{
    "require": {
        "vlucas/phpdotenv": "^2.4"
    },
    "autoload": {
        "psr-4": {
            "Src\\": "src/"
        }
    }
}
7 directory, and the DotEnv dependency is installed (we can also use our autoloader to load our classes from
{
    "require": {
        "vlucas/phpdotenv": "^2.4"
    },
    "autoload": {
        "psr-4": {
            "Src\\": "src/"
        }
    }
}
2 with no
{
    "require": {
        "vlucas/phpdotenv": "^2.4"
    },
    "autoload": {
        "psr-4": {
            "Src\\": "src/"
        }
    }
}
9 calls).

Let’s create a

composer install
0 file for our project with two lines in it, so the
{
    "require": {
        "vlucas/phpdotenv": "^2.4"
    },
    "autoload": {
        "psr-4": {
            "Src\\": "src/"
        }
    }
}
7 directory and our local
{
    "require": {
        "vlucas/phpdotenv": "^2.4"
    },
    "autoload": {
        "psr-4": {
            "Src\\": "src/"
        }
    }
}
4 file will be ignored:

composer install
0

/vendor
.env

Next, we’ll create a

composer install
4 file with one variable:
composer install
5 to hold our secret key (used when generating and verifying JWTs):

composer install
4

SECRET=

and we’ll copy

composer install
4 to
{
    "require": {
        "vlucas/phpdotenv": "^2.4"
    },
    "autoload": {
        "psr-4": {
            "Src\\": "src/"
        }
    }
}
4 where we’ll fill in our actual secret key (it will be ignored by Git so it won’t end up in our repository).

We’ll need a

composer install
9 file which loads our environment variables (later it will also do some additional bootstrapping for our project).

composer install
9

<?php
require 'vendor/autoload.php';
use Dotenv\Dotenv;

$dotenv = new DotEnv(__DIR__);
$dotenv->load();

Let’s create a simple tool

/vendor
.env
1 which will generate a secret key for us, so we can put it in the
{
    "require": {
        "vlucas/phpdotenv": "^2.4"
    },
    "autoload": {
        "psr-4": {
            "Src\\": "src/"
        }
    }
}
4 file:

/vendor
.env
1

<?php

$secret = bin2hex(random_bytes(32));
echo "Secret:\n";
echo $secret;
echo "\nCopy this key to the .env file like this:\n";
echo "SECRET=" . $secret . "\n";

If you run it in the command line, you should get an output like this:

$ php generate_key.php


Secret:
7c32d31dbdd39f2111da0b1dea59e94f3ed715fd8cdf0ca3ecf354ca1a2e3e30
Copy this key to the .env file like this:
SECRET=7c32d31dbdd39f2111da0b1dea59e94f3ed715fd8cdf0ca3ecf354ca1a2e3e30

Follow the instructions and add your secret key to the

{
    "require": {
        "vlucas/phpdotenv": "^2.4"
    },
    "autoload": {
        "psr-4": {
            "Src\\": "src/"
        }
    }
}
4 file (don’t worry, the key in the example above is not used anywhere).

Next we’ll build a tool to generate example JWTs (with a hardcoded payload that you can modify as you wish). First we’ll add a

/vendor
.env
5 function to our
composer install
9 file:

composer install
9

<?php
require 'vendor/autoload.php';
use Dotenv\Dotenv;

$dotenv = new DotEnv(__DIR__);
$dotenv->load();

// PHP has no base64UrlEncode function, so let's define one that
// does some magic by replacing + with -, / with _ and = with ''.
// This way we can pass the string within URLs without
// any URL encoding.
function base64UrlEncode($text)
{
    return str_replace(
        ['+', '/', '='],
        ['-', '_', ''],
        base64_encode($text)
    );
}

Here’s the

/vendor
.env
8 tool:

/vendor
.env
8

data = base64urlEncode(header) + "." + base64urlEncode(payload)
hashedData = hash(data, secret)
signature = base64urlEncode(hashedData)
0

You can run the tool from the command line to get an output like this:

data = base64urlEncode(header) + "." + base64urlEncode(payload)
hashedData = hash(data, secret)
signature = base64urlEncode(hashedData)
1

You can then inspect the token at https://jsonwebtoken.io to see the header and payload and confirm they match the example.

The next tool we’ll build will allow you to validate JWTs created by the

SECRET=
0 tool (by verifying the expiration time and the signature). We’ll use Carbon to help us with the expiration time calculations so let’s add the library:

data = base64urlEncode(header) + "." + base64urlEncode(payload)
hashedData = hash(data, secret)
signature = base64urlEncode(hashedData)
2

Here’s the validation script:

SECRET=
1

data = base64urlEncode(header) + "." + base64urlEncode(payload)
hashedData = hash(data, secret)
signature = base64urlEncode(hashedData)
3

If you want to validate a JWT, you can supply it through the command line:

data = base64urlEncode(header) + "." + base64urlEncode(payload)
hashedData = hash(data, secret)
signature = base64urlEncode(hashedData)
4

You should get an output like this:

data = base64urlEncode(header) + "." + base64urlEncode(payload)
hashedData = hash(data, secret)
signature = base64urlEncode(hashedData)
5

You can experiment by changing the expiration time of the token, changing the secret key between generating and validating the token, modifying the payload without re-generating the signature, etc.

That’s all there is to building and validating JWTs. Of course, you’ll rarely have to do it on your own as there are many libraries for this purpose.

Use JWTs for Access Tokens in PHP

Okta uses JWT access tokens for its implementation of Oauth 2.0. They are signed using private JSON Web Keys (JWK).

The high-level overview of validating an access token looks like this:

  • Retrieve and parse your Okta JSON Web Keys (JWK), which should be checked periodically and cached by your application
  • Decode the access token, which is in JSON Web Token format
  • Verify the signature used to sign the access token
  • Verify the claims found inside the access token

Okta provides a library (Okta JWT Verifier) for PHP which can be integrated into any PHP project to provide seamless verification of Okta access tokens.

How JWT Verifier Works

The Okta JWT Verifier can be installed through composer:

data = base64urlEncode(header) + "." + base64urlEncode(payload)
hashedData = hash(data, secret)
signature = base64urlEncode(hashedData)
6

The library requires a JWT library and a PSR-7 compliant library. You can install an existing one like this:

data = base64urlEncode(header) + "." + base64urlEncode(payload)
hashedData = hash(data, secret)
signature = base64urlEncode(hashedData)
7

Alternatively, you can also provide your own implementation. To create your own adaptor, just implement the

SECRET=
2 in your own class.

Learn More About PHP, JWTs, and Secure Authentication

You can find the whole code example here: GitHub link

If you would like to dig deeper into the topics covered in this article, the following resources are a great starting point:

  • Validating Access Tokens
  • Get Started with PHP + Okta
  • Okta JWT Verifier library
  • What Happens if your JWT is Stolen?
  • Add Authentication to your PHP App in 5 Minutes

Like what you learned today? Follow us on Twitter, like us on Facebook, check us out on LinkedIn, and subscribe to our YouTube channel for more awesome content!