The jwt-logout plug-in uses Redis to add weak state management to JSON Web Tokens (JWTs), enabling proactive logoff for otherwise stateless tokens. You can also use the plug-in to enforce single-device logon — for example, automatically logging off an account from one device when the account logs on from another.
Plug-in type
Authentication and authorization.
Fields
|
Field |
Data type |
Required |
Default value |
Description |
|
jwks |
string |
No |
- |
The JSON string used to verify a JWT. When this plug-in is used together with the jwt-auth plug-in, you do not need to configure this field. For more information, see JSON Web Key (JWK). |
|
clock_skew |
number |
No |
60 |
The allowed clock skew when the exp and iat fields in a JWT are validated. Unit: seconds. |
|
token_header |
string |
No |
Authorization |
The request header from which the JWT is extracted. |
|
token_prefix |
string |
No |
"Bearer" |
The prefix of the header value. After the prefix is removed, the remaining part is used as the JWT. |
|
redis |
Redis |
Yes |
- |
The Redis service configuration. |
|
logout |
Logout |
No |
- |
The configuration for the JWT logoff feature. If not configured, the logoff feature is disabled. |
|
login |
Login |
No |
- |
The configuration for the JWT single-device logon feature. If not configured, the single-device logon feature is disabled. |
The following table describes the configuration fields of the Redis type.
|
Field |
Data type |
Required |
Default value |
Description |
|
service |
string |
Yes |
- |
The name of the Redis service. Examples:
|
|
port |
number |
Yes |
- |
The port number of the Redis service. |
|
username |
string |
No |
- |
The username that is used in the Redis AUTH command. |
|
password |
string |
No |
- |
The password that is used in the Redis AUTH command. |
|
timeout |
number |
No |
1000 |
The timeout period for a Redis command. Unit: milliseconds. |
The following table describes the configuration fields of the Logout type.
|
Field |
Data type |
Required |
Default value |
Description |
|
key_prefix |
string |
No |
higress_jwt_logout_ |
The prefix of the key stored in Redis. |
|
key |
array of string |
No |
["jti"] |
The key in the JWT payload that identifies the JWT. If multiple JWTs share the same key value, they are treated as the same token. If the key is absent from the JWT payload, a 401 invalid token error is returned. |
|
path |
string |
No |
/jwt_logout |
The URL path suffix used to trigger logoff. When the URL path suffix matches the specified string, the account associated with the JWT in the current request is logged off. The JWT is then invalidated. |
|
error_status |
number |
No |
401 |
The HTTP status code returned when a logged-off JWT is used. |
|
error_body |
string |
No |
'{"message":"invalid token"}' |
The response body returned when a logged-off JWT is used. |
|
ttl |
number |
No |
- |
The time to live (TTL) of the key stored in Redis. Unit: seconds. Determines how long the JWT remains invalidated after logoff. If not specified, the TTL equals the value of the exp field in the payload minus the current time. If the payload does not contain the exp field, the default TTL is 86,400 seconds (24 hours). |
The following table describes the configuration fields of the Login type.
|
Field |
Data type |
Required |
Default value |
Description |
|
key_prefix |
string |
No |
higress_jwt_logout_ |
The prefix of the key stored in Redis. |
|
key |
array of string |
No |
["iss","aud","sub"] |
The single-device logon identifier in the JWT payload. If the JWT payload in the current request contains the same field values but a different JWT, the system treats it as a duplicate logon and rejects the request. The request is allowed once the Redis key for the previous JWT expires. If the JWT payload does not contain the field, a 401 invalid token error is returned. |
|
path |
string |
No |
/jwt_login |
The URL path suffix used to trigger forced logon. When the URL path suffix matches the specified string, the account is force-logged on with the JWT in the current request. Any previously logged-on JWT with the same payload characteristics is invalidated. |
|
error_status |
number |
No |
403 |
The HTTP status code returned for duplicate logons. |
|
error_body |
string |
No |
'{"message":"already login on other device"}' |
The response body for duplicate logons. |
|
ttl |
number |
No |
- |
The TTL of the key stored in Redis. Unit: seconds. Determines the validity period of a JWT used for single-device logon. If not specified, the TTL equals the value of the exp field in the payload minus the current time. If the payload does not contain the exp field, the default TTL is 86,400 seconds (24 hours). |
Configuration examples
Use an ApsaraDB for Redis instance
-
Create an ApsaraDB for Redis instance. For more information, see Overview.
-
Enable the logoff and logon configurations. When the plug-in processes a request, one Redis read request for logoff configuration checking and two Redis read requests for logon configuration checking are generated. In rare cases such as a logoff or first-time logon, two additional Redis write requests are generated. To estimate the capacity of the ApsaraDB for Redis instance, you can multiply the throughput of requests processed by the plug-in by 2.
-
After you configure the ApsaraDB for Redis instance, obtain the VPC endpoint of the instance. Example: r-xxxxxxx.redis.rds.aliyuncs.com.
-
Add a service. For DNS Domain Name, select DNS Domain Name. Set the service port to the Redis port (typically 6379), enter the VPC endpoint in the DNS Domain Name field, and for TLS Mode, select TLS Mode. For more information, see Create a service.
-
Add the following content to the plug-in configuration to connect to the ApsaraDB for Redis instance:
redis: service: redis.dns port: 6379If you configure a password for the Redis service, add the following content:
redis: service: redis.dns port: 6379 password: ****** # Enter the password that you specify.
Implement the JWT logoff feature
Use scenarios
JWTs are stateless tokens that remain valid until they expire. The jwt-logout plug-in lets you forcibly invalidate a specific JWT before expiration.
How it works
-
If the request path suffix matches the path in the plug-in configuration, the logoff mechanism is triggered. The ApsaraDB for Redis instance records the JWT for logoff. The Redis key consists of the configured prefix and the key-value pair extracted from the current JWT payload.
-
If the JWT payload in the request matches the characteristics of a key stored in the ApsaraDB for Redis instance, the JWT is treated as invalid and the request is rejected.
-
The default key expiration time is calculated from the
expfield in the JWT payload, so the key persists until the JWT expires.
-
The default recommended logoff key is ["jti"], where
jtiis the payload field that uniquely identifies a JWT. Per the JWT standard, if thejtivalue in the JWT payload matches the value recorded in the ApsaraDB for Redis instance, the JWT is the same token. Therefore, jti can mark the logoff status of a JWT. -
The concatenation rule of the key stored in the ApsaraDB for Redis instance:
<key_prefix><PayloadKey>##<PayloadValue>. PayloadKey is a list of keys that are delimited by number signs (#). PayloadValue is a list of key values that are delimited by number signs (#). Example:higress_jwt_logout_jti#iss##xxxxx#abcde. -
This plug-in can only invalidate the token carried in the current request during logoff. You can also manually add a key in the ApsaraDB for Redis console using the key concatenation rule described above. If the gateway finds that key in the ApsaraDB for Redis instance, it rejects any access request based on the corresponding JWT.
Example
Plug-in configuration:
redis:
service: redis.dns
port: 6379
jwks: |
{
"keys": [
{
"kty": "oct",
"kid": "123",
"k": "hM0k3AbXBPpKOGg__Ql2Obcq7s60myWDpbHXzgKUQdYo7YCRp0gUqkCnbGSvZ2rGEl4YFkKqIqW7mTHdj-bcqXpNr-NOznEyMpVPOIlqG_NWVC3dydBgcsIZIdD-MR2AQceEaxriPA_VmiUCwfwL2Bhs6_i7eolXoY11EapLQtutz0BV6ZxQQ4dYUmct--7PLNb4BWJyQeWu0QfbIthnvhYllyl2dgeLTEJT58wzFz5HeNMNz8ohY5K0XaKAe5cepryqoXLhA-V-O1OjSG8lCNdKS09OY6O0fkyweKEtuDfien5tHHSsHXoAxYEHPFcSRL4bFPLZ0orTt1_4zpyfew",
"alg": "HS256"
}
]
}
logout:
path: "/jwt_logout"
key: ["jti"]
error_status: 401
error_body: |
{"message":"invalid token"}
-
Trigger a logoff.
curl http://xxx.hello.com/test/jwt_logout -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiJ4eHh4IiwiaXNzIjoiYWJjZCIsInN1YiI6InRlc3QiLCJhdWQiOiJ3d3cudGVzdC5jb20iLCJpYXQiOjE2NjU2NjA1MjcsImV4cCI6MTg2NTY3MzgxOX0.tmKF6qc1mOWNyCCzBOT2XKNoEGeEgr3EbhTKAQfq1io' # The following result is returned: {"message": "logout success"}Token payload:
{ "jti": "xxxx", "iss": "abcd", "sub": "test", "aud": "www.test.com", "iat": 1665660527, "exp": 1865673819 }In this case, the key
higress_jwt_logout_jti##xxxxis stored in the ApsaraDB for Redis instance. -
After the logoff, access with the same JWT is rejected.
curl http://xxx.hello.com/test/abc -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiJ4eHh4IiwiaXNzIjoiYWJjZCIsInN1YiI6InRlc3QiLCJhdWQiOiJ3d3cudGVzdC5jb20iLCJpYXQiOjE2NjU2NjA1MjcsImV4cCI6MTg2NTY3MzgxOX0.tmKF6qc1mOWNyCCzBOT2XKNoEGeEgr3EbhTKAQfq1io' # The following result is returned: {"message":"invalid token"}
Implement forced logoff based on JWTs
Use scenarios
When an account is logged on to multiple devices with JWT authentication, different JWTs may be issued on each device. The jwt-logout plug-in can restrict the account to a single active device at a time.
How it works
-
When a request arrives, the system extracts the JWT and constructs a Redis key from the configured prefix and the key-value pair in the JWT payload. It then queries the corresponding value in the ApsaraDB for Redis instance. If the stored value does not match the current JWT, the request is rejected.
-
If no value exists, the current JWT is written to the Redis key. The default key expiration time is calculated from the
expfield in the JWT payload, so the key persists until the JWT expires. During the JWT's validity period, requests using other JWTs with the same payload characteristics are rejected. -
If the request suffix matches the
pathin the plug-in configuration, the forced logon mechanism is triggered and the current JWT overwrites the associated Redis key. The previously logged-on JWT with the same payload characteristics is invalidated, ensuring single-device logon.
-
The default recommended logon key is ["iss","aud","sub"]. In the key,
issindicates the JWT issuer,audindicates the intended audience, andsubindicates the subject. In most cases, this combination is sufficient to enforce single-device logon. -
The concatenation rule of the key stored in the ApsaraDB for Redis instance:
<key_prefix><PayloadKey>##<PayloadValue>. PayloadKey is a list of keys that are delimited by number signs (#). PayloadValue is a list of key values that are delimited by number signs (#). Example:higress_jwt_login_iss#aud#sub##xxxxx#abcde#fffff. -
For JWTs with the same payload characteristics, the first JWT used in a request is automatically written to the ApsaraDB for Redis instance, enabling single-device logon without an explicit API call. You only need to call the forced logon API when you want to force-replace the active session.
-
When the logoff logic is triggered, the logon key of the current JWT stored in the ApsaraDB for Redis instance is also cleared. This applies when you enable the JWT logoff feature.
Example
Plug-in configuration:
redis:
service: redis.dns
port: 6379
jwks: |
{
"keys": [
{
"kty": "oct",
"kid": "123",
"k": "hM0k3AbXBPpKOGg__Ql2Obcq7s60myWDpbHXzgKUQdYo7YCRp0gUqkCnbGSvZ2rGEl4YFkKqIqW7mTHdj-bcqXpNr-NOznEyMpVPOIlqG_NWVC3dydBgcsIZIdD-MR2AQceEaxriPA_VmiUCwfwL2Bhs6_i7eolXoY11EapLQtutz0BV6ZxQQ4dYUmct--7PLNb4BWJyQeWu0QfbIthnvhYllyl2dgeLTEJT58wzFz5HeNMNz8ohY5K0XaKAe5cepryqoXLhA-V-O1OjSG8lCNdKS09OY6O0fkyweKEtuDfien5tHHSsHXoAxYEHPFcSRL4bFPLZ0orTt1_4zpyfew",
"alg": "HS256"
}
]
}
login:
path: "/jwt_login"
key: ["iss","aud","sub"]
error_status: 403
error_body: |
{"message":"already login on other device"}
-
Perform a successful logon operation.
curl http://xxx.hello.com/test/abc -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiJ6enp6IiwiaXNzIjoiYWJjZCIsImF1ZCI6Ind3dy5leGFtcGxlLmNvbSIsInN1YiI6InRlc3QiLCJpYXQiOjE2NjU2NjA1MjcsImV4cCI6MTg2NTY3MzgxOX0.WljMr5ucxfLF8SmeaaL25c0QG3IX04HoD0als9gglYg'Token payload:
{ "jti": "zzzz", "iss": "abcd", "aud": "www.example.com", "sub": "test", "iat": 1665660527, "exp": 1865673819 }In this case, the key
higress_jwt_login_iss#aud#sub##abcd#www.example.com#abcdis stored in the ApsaraDB for Redis instance and the value is the same as that in the current JWT. -
Reject the request when a different JWT with the same payload characteristics is used.
curl http://xxx.hello.com/test/abc -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjEyMyJ9.eyJqdGkiOiJ5eXl5eSIsImlzcyI6ImFiY2QiLCJhdWQiOiJ3d3cuZXhhbXBsZS5jb20iLCJzdWIiOiJ0ZXN0IiwiaWF0IjoxNjY1NjYwNTI5LCJleHAiOjE4NjU2NzM4MTl9.6vi6eKPWSKHQxfzBPrj3-SWI4Q5zGtWhqp38JIN3FEo' # The following result is returned: {"message":"already login on other device"}Token payload:
{ "jti": "yyyyy", "iss": "abcd", "aud": "www.example.com", "sub": "test", "iat": 1665660527, "exp": 1865673819 }The payload characteristics match those of the JWT in Step 1, but the
jtifield differs. Therefore, the request is rejected. -
Perform a forced logon.
curl http://xxx.hello.com/test/jwt_login -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjEyMyJ9.eyJqdGkiOiJ5eXl5eSIsImlzcyI6ImFiY2QiLCJhdWQiOiJ3d3cuZXhhbXBsZS5jb20iLCJzdWIiOiJ0ZXN0IiwiaWF0IjoxNjY1NjYwNTI5LCJleHAiOjE4NjU2NzM4MTl9.6vi6eKPWSKHQxfzBPrj3-SWI4Q5zGtWhqp38JIN3FEo' # The following result is returned: {"message":"login success"}In this case, the key
higress_jwt_login_iss#aud#sub##abcd#www.example.com#abcdis stored in the ApsaraDB for Redis instance and the value is replaced by the value in the current JWT.Use the current JWT to successfully access the page.
curl http://xxx.hello.com/test/abc -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjEyMyJ9.eyJqdGkiOiJ5eXl5eSIsImlzcyI6ImFiY2QiLCJhdWQiOiJ3d3cuZXhhbXBsZS5jb20iLCJzdWIiOiJ0ZXN0IiwiaWF0IjoxNjY1NjYwNTI5LCJleHAiOjE4NjU2NzM4MTl9.6vi6eKPWSKHQxfzBPrj3-SWI4Q5zGtWhqp38JIN3FEo'If you use the JWT in Step 1, a failure message is returned.
curl http://xxx.hello.com/test/abc -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjEyMyJ9.eyJqdGkiOiJ4eHh4IiwiaXNzIjoiYWJjZCIsImF1ZCI6Ind3dy5leGFtcGxlLmNvbSIsInN1YiI6InRlc3QiLCJpYXQiOjE2NjU2NjA1MjcsImV4cCI6MTg2NTY3MzgxOX0.P0WtBTHJzUJvklu9q8XSRszfPbgojrZHg7t4ZaYfKGo' # The following result is returned: {"message":"already login on other device"}
Error codes
|
HTTP status code |
Error message |
Reason |
|
401 |
invalid token |
No JWT is provided in the request header, the JWT format is invalid, or the JWT has expired. |
|
500 |
redis server error |
The ApsaraDB for Redis instance timed out or is unreachable. |