diff --git a/auth_jwt/models/auth_jwt_validator.py b/auth_jwt/models/auth_jwt_validator.py
index 13649adad2..efe4332a0f 100644
--- a/auth_jwt/models/auth_jwt_validator.py
+++ b/auth_jwt/models/auth_jwt_validator.py
@@ -27,6 +27,12 @@
_logger = logging.getLogger(__name__)
AUTHORIZATION_RE = re.compile(r"^Bearer ([^ ]+)$")
+SECRET_ALGORITHM_SELECTION = [
+ # https://pyjwt.readthedocs.io/en/stable/algorithms.html
+ ("HS256", "HS256 - HMAC using SHA-256 hash algorithm"),
+ ("HS384", "HS384 - HMAC using SHA-384 hash algorithm"),
+ ("HS512", "HS512 - HMAC using SHA-512 hash algorithm"),
+]
class AuthJwtValidator(models.Model):
@@ -39,12 +45,7 @@ class AuthJwtValidator(models.Model):
)
secret_key = fields.Char()
secret_algorithm = fields.Selection(
- [
- # https://pyjwt.readthedocs.io/en/stable/algorithms.html
- ("HS256", "HS256 - HMAC using SHA-256 hash algorithm"),
- ("HS384", "HS384 - HMAC using SHA-384 hash algorithm"),
- ("HS512", "HS512 - HMAC using SHA-512 hash algorithm"),
- ],
+ SECRET_ALGORITHM_SELECTION,
default="HS256",
)
public_key_jwk_uri = fields.Char()
@@ -97,6 +98,17 @@ class AuthJwtValidator(models.Model):
cookie_secure = fields.Boolean(
default=True, help="Set to false only for development without https."
)
+ renew_cookie_on_response = fields.Boolean(
+ help="Renew the cookie in every response to stay the client "
+ "authenticatedas long it use the API. Don't mark unless you have a "
+ "way to invalidate sessions",
+ default=True,
+ )
+ renew_cookie_secret = fields.Char()
+ renew_cookie_algorithm = fields.Selection(
+ SECRET_ALGORITHM_SELECTION,
+ default="HS256",
+ )
_sql_constraints = [
("name_uniq", "unique(name)", "JWT validator names must be unique !"),
@@ -163,26 +175,40 @@ def _get_key(self, kid):
jwks_client = PyJWKClient(self.public_key_jwk_uri, cache_keys=False)
return jwks_client.get_signing_key(kid).key
- def _encode(self, payload, secret, expire):
+ def _encode(self, payload, expire, secret=False):
"""Encode and sign a JWT payload so it can be decoded and validated with
_decode().
The aud and iss claims are set to this validator's values.
The exp claim is set according to the expire parameter.
"""
+ if secret:
+ key = secret
+ algorithm = "HS256"
+ elif self.renew_cookie_on_response:
+ key = self.renew_cookie_secret
+ algorithm = self.renew_cookie_algorithm
+ elif self.signature_type == "secret":
+ key = self.secret_key
+ algorithm = self.secret_algorithm
+ else:
+ raise ConfigurationError(_("The token cannot be encoded with public key"))
payload = dict(
payload,
exp=timegm(datetime.datetime.utcnow().utctimetuple()) + expire,
aud=self.audience,
iss=self.issuer,
)
- return jwt.encode(payload, key=secret, algorithm="HS256")
+ return jwt.encode(payload, key=key, algorithm=algorithm)
- def _decode(self, token, secret=None):
+ def _decode(self, token, secret=None, cookie_secret=False):
"""Validate and decode a JWT token, return the payload."""
if secret:
key = secret
algorithm = "HS256"
+ elif self.renew_cookie_on_response and cookie_secret:
+ key = self.renew_cookie_secret
+ algorithm = self.renew_cookie_algorithm
elif self.signature_type == "secret":
key = self.secret_key
algorithm = self.secret_algorithm
@@ -291,13 +317,6 @@ def unlink(self):
self._unregister_auth_method()
return super().unlink()
- def _get_jwt_cookie_secret(self):
- secret = self.env["ir.config_parameter"].sudo().get_param("database.secret")
- if not secret:
- _logger.error("database.secret system parameter is not set.")
- raise ConfigurationError()
- return secret
-
@api.model
def _parse_bearer_authorization(self, authorization):
"""Parse a Bearer token authorization header and return the token.
diff --git a/auth_jwt/models/ir_http.py b/auth_jwt/models/ir_http.py
index b65118fd88..bdfcd66a4d 100644
--- a/auth_jwt/models/ir_http.py
+++ b/auth_jwt/models/ir_http.py
@@ -65,7 +65,7 @@ def _get_jwt_payload(cls, validator):
raise
token = cls._get_cookie_token(validator.cookie_name)
assert token
- return validator._decode(token, secret=validator._get_jwt_cookie_secret())
+ return validator._decode(token, cookie_secret=True)
@classmethod
def _auth_method_jwt(cls, validator_name=None):
@@ -91,7 +91,7 @@ def _auth_method_jwt(cls, validator_name=None):
raise list(exceptions.values())[0]
raise UnauthorizedCompositeJwtError(exceptions)
- if validator.cookie_enabled:
+ if validator.cookie_enabled and validator.renew_cookie_on_response:
if not validator.cookie_name:
_logger.info("Cookie name not set for validator %s", validator.name)
raise ConfigurationError()
@@ -99,7 +99,6 @@ def _auth_method_jwt(cls, validator_name=None):
key=validator.cookie_name,
value=validator._encode(
payload,
- secret=validator._get_jwt_cookie_secret(),
expire=validator.cookie_max_age,
),
max_age=validator.cookie_max_age,
diff --git a/auth_jwt/views/auth_jwt_validator_views.xml b/auth_jwt/views/auth_jwt_validator_views.xml
index bc907038a9..583a969b0d 100644
--- a/auth_jwt/views/auth_jwt_validator_views.xml
+++ b/auth_jwt/views/auth_jwt_validator_views.xml
@@ -68,6 +68,24 @@
name="cookie_max_age"
attrs="{'invisible': [('cookie_enabled', '=', False)]}"
/>
+
+
+
+
diff --git a/auth_jwt_demo/demo/auth_jwt_validator.xml b/auth_jwt_demo/demo/auth_jwt_validator.xml
index 6bba454a67..63e5663720 100644
--- a/auth_jwt_demo/demo/auth_jwt_validator.xml
+++ b/auth_jwt_demo/demo/auth_jwt_validator.xml
@@ -24,6 +24,8 @@
demo_auth
+
+ renew_cookie_secret
demo_keycloak