The jwt-logout plug-in uses Redis to implement weak state management for JSON Web Tokens (JWTs), enabling proactive JWT invalidation and single-device logon control. For example, when a user logs on from a new device, the previous session is automatically invalidated.
Plug-in type
Plug-in for authentication and authorization.
Fields
|
Field |
Data type |
Required |
Default value |
Description |
|
jwks |
string |
No |
- |
JSON string for JWT verification. Not required when this plug-in is used with the jwt-auth plug-in. For more information, see JSON Web Key (JWK). |
|
clock_skew |
number |
No |
60 |
Allowed clock skew when the exp and iat fields in a JWT are verified. Unit: seconds. |
|
token_header |
string |
No |
Authorization |
Request header from which the JWT is extracted. |
|
token_prefix |
string |
No |
"Bearer" |
Prefix of the header value. After the prefix is removed, the remaining part is used as the JWT. |
|
redis |
Redis |
Yes |
- |
Redis service configuration. |
|
logout |
Logout |
No |
- |
Configuration for the JWT logoff feature. If not configured, JWT logoff is disabled. |
|
login |
Login |
No |
- |
Configuration for the JWT single-device logon feature. If not configured, single-device logon is disabled. |
The following table describes the configuration fields of the Redis type.
|
Field |
Data type |
Required |
Default value |
Description |
|
service |
string |
Yes |
- |
Name of the Redis service. Examples:
|
|
port |
number |
Yes |
- |
Port number of the Redis service. |
|
username |
string |
No |
- |
Username for the Redis AUTH command. |
|
password |
string |
No |
- |
Password for the Redis AUTH command. |
|
timeout |
number |
No |
1000 |
Timeout period for Redis commands. 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_ |
Prefix of the key stored in Redis. |
|
key |
array of string |
No |
["jti"] |
Key in the JWT payload that identifies a JWT. If the same key appears in multiple JWT payloads, the JWTs are treated as identical. If the key is absent from the JWT payload, a 401 invalid token error is returned. |
|
path |
string |
No |
/jwt_logout |
String used to match the URL path suffix. If matched, the JWT in the current request is invalidated and can no longer be used. |
|
error_status |
number |
No |
401 |
HTTP status code returned when a logged-off JWT is used. |
|
error_body |
string |
No |
'{"message":"invalid token"}' |
Response body returned when a logged-off JWT is used. |
|
ttl |
number |
No |
- |
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_ |
Prefix of the key stored in Redis. |
|
key |
array of string |
No |
["iss","aud","sub"] |
Single-device logon identifier in the JWT payload. If the current request JWT contains the same field values as a previously authenticated JWT but is not the exact same JWT, the request is rejected as a duplicate logon. Access is denied until the Redis key for the authenticated JWT expires. If the field is absent from the JWT payload, a 401 invalid token error is returned. |
|
path |
string |
No |
/jwt_login |
String used to match the URL path suffix. If matched, the current request JWT becomes the active session token, and any previously authenticated JWT with the same payload characteristics is invalidated. |
|
error_status |
number |
No |
403 |
HTTP status code returned for duplicate logon attempts. |
|
error_body |
string |
No |
'{"message":"already login on other device"}' |
Response body returned for duplicate logon attempts. |
|
ttl |
number |
No |
- |
TTL of the key stored in Redis. Unit: seconds. Determines the validity period of a single-device logon JWT. 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. Select DNS Domain Name from the Service Source drop-down list, enter the Redis port number (6379 in most cases) in the Service Port field, enter the VPC endpoint in the Domain Names field, and select Disabled from the TLS Mode drop-down list. 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 expiration. Use the jwt-logout plug-in to force-invalidate a specific JWT before it expires.
How it works
-
If the suffix of the request path matches the path in the plug-in configuration, the logoff mechanism is triggered. The ApsaraDB for Redis instance records the JWT for the logoff. The key stored in the ApsaraDB for Redis instance consists of the configured prefix and the key and value extracted from the current JWT payload.
-
If the payload of the JWT carried in the request matches the characteristics of the key stored in the ApsaraDB for Redis instance, the system considers the current JWT invalid and rejects the request.
-
The default expiration time of the key stored in the ApsaraDB for Redis instance is calculated based on the
expfield in the JWT payload. This means that the key can be stored until the JWT expires.
-
The default recommended logoff key of the plug-in is ["jti"], in which
jtiis the payload field that uniquely identifies a JWT. As specified in JWT standards, if thejtivalue in the JWT payload is the same as that recorded in the ApsaraDB for Redis instance, the entire JWT is exactly the same. Therefore, jti can be used to 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 that is carried in the current request during the logoff. You can also manually specify the token in the ApsaraDB for Redis console based on the preceding key concatenation rule. If the gateway finds that the key exists in the ApsaraDB for Redis instance, the gateway rejects the access request that is based on the relevant 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 based on the JWT is not allowed.
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 logs on from multiple devices with JWT authentication, different JWTs may be issued for each device. Use the jwt-logout plug-in to enforce single-device logon so only one session remains active at a time.
How it works
-
When a request is initiated, the system extracts the JWT from the current request and constitutes a Redis key based on the configured prefix and the key and value extracted from the current JWT payload. Then, the system queries the value that corresponds to the key in the ApsaraDB for Redis instance. If the value is inconsistent with the key value of the current JWT, the system rejects the access request.
-
If the value does not exist, the current JWT is written to the Redis key. The default expiration time of the key in the ApsaraDB for Redis instance is calculated based on the
expfield in the JWT payload. This means that the key can be stored until the JWT expires. During the validity period of the JWT, access requests based on other JWTs that have the same payload characteristics are not allowed. -
If the request suffix matches the
pathin the plug-in configuration, the forced logoff mechanism is triggered and the current JWT is written to the value of the associated Redis key. The JWT that is used for a successful logon and has the same payload characteristics is invalidated upon logoff. This ensures single-device logon.
-
The default recommended logon key of the plug-in is ["iss","aud","sub"]. In the key,
issindicates the JWT issuer,audindicates the JWT use scenario, andsubindicates the subject of the JWT. In most cases, the combination can be used to ensure 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 the JWTs that have the same payload characteristics, the first JWT used for requests is automatically written to the ApsaraDB for Redis instance. This helps implement the single-device logon feature of the plug-in. Therefore, you do not need to call the API operation for forced logon. You need to call the API operation only when forced logoff is required in logon scenarios.
-
When you trigger the logoff logic, the logon key of the current JWT stored in the ApsaraDB for Redis instance is cleared. This rule also 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 an access request when the JWT payload in the request has the same payload characteristics.
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 are the same for the current JWT and the JWT in Step 1. However, the non-characteristic field
jtiis different between the two JWTs. Therefore, the access request based on the JWT 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 is expired or has been invalidated. |
|
500 |
redis server error |
The ApsaraDB for Redis instance is unreachable or the connection timed out. |