Ultimate guide to WordPress JWT Authentication

Note! In this guide I’m using free Advanced Access Manager (aka AAM) plugin 6.0.0 or higer to facilitate JWT signing and validation process.

JWT token and user authentication is becoming widely popular. Per IETF description, JSON Web Token (JWT) is a compact, URL-safe means of representing claims to be transferred between two parties.

This guide is prepared with two assumptions:

  • – that you know enough about JWT and if you do not, please take some time to get familiar with it. The concept is super simple and can be mastered reasonably fast;
  • – that you already have properly installed WordPress instance and if there is a different application that either issues JWT or consumes it, that you already have all necessary implementation that handles JWT token. If that is not the case, there are countless resources that have examples of a code that work with JWT (e.g Node.js or C# .NET Core).

In the rest of this guide, we’ll be focusing on two different ways to sign and verify JWT token – with symmetric and asymmetric (public/private cert pair) keys.

It makes sense to use a symmetric key when the same WordPress instance issues and consumes a token; besides this is the default method AAM uses that does not require any additional configurations. However, it is recommended to use an asymmetric key for integration with third-party applications.

A bit of the prep talk

To make sure that we all are on the same page, let’s define the high-level picture for all the moving parts.

The JWT token is used to authenticate HTTP request to a WordPress website. When JWT token is sent correctly, it basically tells a website’s core something like “Hey, my name is Joe and here is my identification (a JWT token) that was issued by the party that you (a website core) trust”.

JWT token does not authorize any activities, so technically it should never be used to implement any code that allows or denies specific actions (e.g. create a post, delete user, update settings). Authorization part has to be controlled for the account that JWT token represents and that is something we’ll discuss down the road.

This is the high-level diagram of how all this actually works.

WordPress JWT Authentication

Basically, HTTP Client (e.g. user’s browser, application, Postman, Swagger, etc.) sends request with included JWT token that is validated by AAM Auth Handler and if valid, AAM provides to the WP Core claimed User ID in the token. This way the rest of the HTTP request is processed as if a user was actually logged in.

Because JWT token is associated with some active website account, you have the opportunity to manage access to a website’s resources and actions for that particular account. AAM has hundreds of features that you can utilize to define as granular access as needed. To be even more compliant with enterprise-level security standards, you can prepare access policies and attach them to any user.

Note! When JWT token is valid, it does not necessarily mean that it can be successfully used because the associated account can be blocked by a website administrator or expired. To learn more about managing website users please refer to the How to manage WordPress users article.

Now, we established the base terminology and idea so it is time to show how to actually implement an authentication process with symmetric and asymmetric keys.

JWT signed with symmetric key

If you are not familiar with a symmetric key, think about it as some secret string that is shared with two parties – one party that issues JWT token and another party, that validates it. AAM, by default, uses this method and for the secret key, the WordPress core SECURE_AUTH_KEY value is used (you can find this value defined in your website main wp-config.php file that is typically in the root of a website).

Note! You can redefine or periodically rotate the secret key with ConfigPress option authentication.jwt.secret. That would be strongly recommended action if you need to share a secret with other application which is not the one that issues tokens.

JWT signed with asymmetric key

Another way to sign JWT token is to use asymmetric keys (other words – public and private certificates). In this case several additional configurations have to be entered on the ConfigPress tab. If you do not have already generated a certificate pair, let’s generate a private key as well as a public key that is associated with it. The below two commands will do the magic:

ssh-keygen -t rsa -b 4096 -m PEM -f jwtRS256.key
openssl rsa -in jwtRS256.key -pubout -outform PEM -out jwtRS256.key.pub

The first command ssh-keygen generates the private key while the second command openssl consumes the private key to generate the public pair certificate.

Now that you have those two files jwtRS256.key and jwtRS256.key.pub, you can distribute them accordingly. For example, if your WordPress website is the one that issues JWT token, then you need to store securely jwtRS256.key certificate file on your website server and configure AAM to read this file during the JWT issuing process. To do so, go to the ConfigPress tab and use authentication.jwt.privateKeyPath settings where you specify the absolute path to the private key.

[aam]
authentication.jwt.algorithm = "RS256"
authentication.jwt.privateKeyPath = "/var/mydomain.com/certs/jwtRS256.key"

Note! We also redefine the default JWT signing algorithm to asymmetric RS256 with authentication.jwt.algorithm setting because the default algorithm that AAM users is symmetric HS256.

If your website is the one that consumes JWT token, which means that it either authenticates HTTP request, with URL or programmatically validates token through RESTful endpoint /aam/v2/jwt/validate, then you need to point AAM to the public certificate that should be located on your website server. Use authentication.jwt.publicKeyPath ConfigPress setting for that.

[aam]
authentication.jwt.algorithm = "RS256"
authentication.jwt.publicKeyPath = "/var/mydomain.com/certs/jwtRS256.key.pub"

Note! Your website can be both issuer and consumer of a token, in this case make sure that you have both certification files stored securely on your website.

JWT token claims

Each JWT token that is issued with AAM comes with few simple claims. The most important is userId that contains numeric value for the valid user account in the system.

Another important flag is revocable. Depending on its value, it determines if AAM has to perform additional validation against JWT token registry that each account has. By default all the issued tokens are revocable.

Last but not least, with AAM 5.9.4 we’ve introduced the new refreshable flag that determines if the token can be used to obtain a new token for the same time duration. You can learn a bit more about this from the authentication.jwt.refreshable plugin reference section.

{
  "iat": 1553820141,
  "iss": "https://aamplugin.com",
  "exp": 1573225283,
  "jti": "b69fc282-2af4-4222-8d81-f405fc6acb8e",
  "userId": 1,
  "revocable": true,
  "refreshable": false
}

For Developers! You have the ability to alter the default set of claims by using aam_jwt_claims_filter filter that accepts the associated array of claims. It also has to return the valid associated array of claims that will be used to issue a JWT token.

RESTful JWT RESTful API

AAM plugin comes with few custom RESTful endpoints that facilitate JWT life-cycle as following:

  • /aam/v2/authenticate: authenticates user with username/password pair and returns revocable JWT token that is valid for 24 hours (time-to-live is configurable);
  • /aam/v2/jwt/validate: validates provided token and returns the list of all claims in it;
  • /aam/v2/jwt/refresh(AAM 6.0.0 or higher): reissue still valid JWT token for the same period of time (e.g. token expires is 72 hours, AAM will reissue a new token for another 72 hours). This way your application can automatically reissue almost expired token without the need to reenter credentials or manually reissue new token with AAM UI.
  • /aam/v2/jwt/revoke(AAM 6.0.0 or higher): programatically revoke registered and valid JWT token;

To keep this guide a bit shorter, this Postman collection may answer most of your questions:

As you might notice, AAM issues revocable JWT tokens, which means that any token that is issued, is stored in the internal system registry and can be deleted by the webmaster at any time. This way if you start noticing suspicious activity or aware that token was compromised, you can just remove it from the associated account and it will be no longer valid token.

Another fact about JWT token is that by default it expires in 24 hours however this is configurable value with authentication.jwt.expires ConfigPress setting where you can specify different ttl (time to live) in seconds.

[aam]
; keep JWT token valid for 2 hours
authentication.jwt.expires = 7200

Finally, it is important to emphasize that AAM keeps a limited number of issued JWT tokens per account (user). This prevents the website from being overloaded with a large number of issued tokens either by accident or on purpose. The default value is 10 tokens per account and AAM implements the ring-buffer approach where the first token in the list is removed before a new token is added to the end of the list.

This limitation is also configurable with authentication.jwt.registryLimit ConfigPress option. However it is strongly recommended to keep this number small as with a big number of tokens, the risk to have one of the compromised gets exponentially higher.

UI (user interface) for JWT token management

As it has been mentioned above, each JWT token is associated with specific user account that is why it makes sense that list of token is managed on user level. Go to AAM page and on the Users/Roles Manager panel click on Users tab. Then find that account you are interested in and click on Manage User button. The JWT Tokens feature should be listed on the Main panel.

WordPress JWT Tokens

Note! If you need to give access to the JWT Tokens feature for other users that have access to AAM page, make sure that aam_manage_jwt capability is assigned to that user.

The UI is pretty basic. Here you can see the list of all issued tokens and their status (valid or invalid). You can issue new tokens, view or revoke (basically delete) any existing JWT token.

WordPress JWT Manager

Authenticate HTTP request with JWT token

Finally, let’s talk about authenticating HTTP request to the WordPress website with valid JWT token.

Foremost you have to somehow pass the JWT token in the actual HTTP request and AAM offers several ways to do that. This way you can use one or more methods to integrate your application with the WordPress core.

Currently AAM supports following methods to pass a token:

HTTP request Header. This is the default method and AAM expects that HTTP request will contain Authentication header with Bearer JWT token.

Note! AAM does not use standard Authorization header as it is skipped by most Apache servers. Instead of doing all these crazy hacks in the .htaccess or Apache configs, you can simply use Authentication header.

Here is the example of the HTTP GET request with JWT token included

GET /wp-json/wp/v2/posts/1 HTTP/1.1
Host: example.org
Authentication: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE1MjE0NTI5NDQsImV4cCI6MTUyMTUzOTM0NCwidXNlcklkIjoxfQ.uTAmX11NAYZ1qddRzwDW3iFa--sIqkps9j35LG0CP1o
Cache-Control: no-cache

Superglobal $_POST. If JWT token is not in the HTTP Header, AAM fallback to check the $_POST array for the aam-jwt value. So basically you can have JWT token placed as hidden field on the HTML form.

Superglobal $_GET. If JWT token is not in the $_POST, AAM fallback to check the $_GET array for the aam-jwt parameter. For example this method is used to authenticate user with URL as mentioned in the Temporary user account, login with URL & JWT token article.

Superglobal $_COOKIE. If JWT token is not in the $_GET, AAM fallback to check the $_COOKIE array for the aam_jwt_token cookie.

Custom. If none of the above options satisfy your needs, you have the ability to implement custom code that will return JWT token. Use aam_extract_jwt_filter filter that accepts two arguments – the first argument is null while the second is the custom method type:

apply_filters('aam_extract_jwt_filter', null, string $method)

This also means that you need to register your custom method with the help of authentication.jwt.container ConfigPress option:

[aam]
; 1st check Authentication header and if not there, then trigger 'aam-get-jwt-filter' hook
authentication.jwt.container = "header,xheader"

Get notified about important updates and new features (no more than one email per month).