The jwt-logout plug-in uses Redis to implement weak state management for JSON Web Tokens (JWTs). The plug-in resolves the issue that JWTs do not support proactive logoff. You can also use the plug-in to implement single-device logon of an account. For example, an account is automatically logged off from a device when the account is used to log on to another device.
Plug-in type
Plug-in for authentication and authorization.
Fields
Field | Data type | Required | Default value | Description |
jwks | string | No | - | The JSON string that is used to verify a JWT. When you use this plug-in 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 clock offset that is allowed when the exp and iat fields in a JWT are checked. Unit: seconds. |
token_header | string | No | Authorization | The request header from which you can extract a JWT. |
token_prefix | string | No | "Bearer" | The prefix of the value in the request header. After the prefix is removed, the remaining part of the request header is used as the JWT content. |
redis | Redis | Yes | - | The Redis service configuration. |
logout | Logout | No | - | The configuration that is used to implement the JWT logoff feature. If this field is not configured, the JWT logoff feature is disabled. |
login | Login | No | - | The configuration that is used to implement the JWT single-device logon feature. If this field is not configured, the JWT 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 of a Redis command. Unit: millisecond. |
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 to identify a JWT. If the same key is contained in the payloads of multiple JWTs, the JWTs are considered the same one. If the key is not contained in the JWT payload, the error 401 invalid token is reported. |
path | string | No | /jwt_logout | The character string that is used to match the URL path suffix. If the URL path suffix matches the specified string, the account that uses the JWT in the current request is logged off. The JWT cannot be used any more. |
error_status | number | No | 401 | The error code that is returned upon logoff. |
error_body | string | No | '{"message":"invalid token"}' | The body of the response upon logoff. |
ttl | number | No | - | The time to live (TTL) of the key stored in Redis. Unit: seconds. This configuration determines the duration in which the JWT is invalidated after logoff. If this configuration is not specified, the TTL is equal to 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 contain the same field but the request JWT is not exactly the same as the JWT of a successful logon, the system considers the current request as a repeated logon request and rejects the request. The system allows the request until the key of the JWT for the successful logon in Redis expires. If the JWT payload in the current request does not contain the field, the error 401 invalid token is reported. |
path | string | No | /jwt_login | The character string that is used to match the URL path suffix. If the URL path suffix matches the specified string, the account is forced to log on to the system by using the JWT in the current request. The JWT that is used for a successful logon and has the same payload characteristics is invalidated upon logoff. |
error_status | number | No | 403 | The error code that is returned for repeated logons. |
error_body | string | No | '{"message":"already login on other device"}' | The response body for repeated logons. |
ttl | number | No | - | The TTL of the key stored in Redis. Unit: seconds. The field determines the validity period of a JWT that is used for single-device logon. If this field is not specified, the TTL is equal to 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. If a JWT is issued, it is valid until it expires. You can use the jwt-logout plug-in to implement forced logoffs when a specific JWT is used.
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 is used for logon to multiple devices and JWT authentication is used for logon, different JWTs may be issued on different devices. In this case, you can use the jwt-logout plug-in to allow the account to log on to only one device 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. |
500 | redis server error | Access to the ApsaraDB for Redis instance times out or fails. |