Skip to content

MFA cookie only works for one stage. #20142

@jeroen1602

Description

@jeroen1602

Is your feature request related to a problem?

I have a use-case where I want the MFA to be required and requested more frequently for apps where sensitive data can be manipulated.

But in other apps I want it to be required, but not asked as many times.

The MFA has the option for Last validation threshold which gives the impression of doing this.

However the MFA cookie also stores the stage it was used for. So it will not work when you have a second flow and/or stage.

Describe the solution you'd like

I would like to be able to use multiple MFA stages and share the MFA threshold.

A way that I though about fixing it is as follows:

In the authenticator_validate stage. Create a MFA cookie with:

  • auth device
  • issued at
  • Possible still an expiration, but not the same as the stage's limit.

Then the stage can compare the issued at date against its own set threshold. That way the same token can be reused across stages. It would also need to check if the device used is a device allowed for this stage.

See the set_valid_mfa_cookie

def set_valid_mfa_cookie(self, device: Device) -> HttpResponse:
"""Set an MFA cookie to allow users to skip MFA validation in this context (browser)
The cookie is JWT which is signed with a hash of the secret key and the UID of the stage"""
stage: AuthenticatorValidateStage = self.executor.current_stage
delta = timedelta_from_string(stage.last_auth_threshold)
if delta.total_seconds() < 1:
self.logger.info("Not setting MFA cookie since threshold is not set.")
return self.executor.stage_ok()
expiry = datetime.now() + delta
cookie_payload = {
"device": device.pk,
"stage": stage.pk.hex,
"exp": expiry.timestamp(),
}
response = self.executor.stage_ok()
cookie = encode(cookie_payload, self.cookie_jwt_key)
response.set_cookie(
COOKIE_NAME_MFA,
cookie,
expires=expiry,
path=settings.SESSION_COOKIE_PATH,
domain=settings.SESSION_COOKIE_DOMAIN,
samesite=settings.SESSION_COOKIE_SAMESITE,
)
return response

And check_mfa_cookie

def check_mfa_cookie(self, allowed_devices: list[Device]):
"""Check if an MFA cookie has been set, whether it's valid and applies
to the current stage and device.
The list of devices passed to this function must only contain devices for the
correct user and with an allowed class"""
if COOKIE_NAME_MFA not in self.request.COOKIES:
return
stage: AuthenticatorValidateStage = self.executor.current_stage
threshold = timedelta_from_string(stage.last_auth_threshold)
latest_allowed = datetime.now() + threshold
try:
payload = decode(self.request.COOKIES[COOKIE_NAME_MFA], self.cookie_jwt_key, ["HS256"])
if payload["stage"] != stage.pk.hex:
self.logger.warning("Invalid stage PK")
return
if datetime.fromtimestamp(payload["exp"]) > latest_allowed:
self.logger.warning("Expired MFA cookie")
return
if not any(device.pk == payload["device"] for device in allowed_devices):
self.logger.warning("Invalid device PK")
return
self.logger.info("MFA has been used within threshold")
raise FlowSkipStageException()
except (PyJWTError, ValueError, TypeError) as exc:
self.logger.info("Invalid mfa cookie for device", exc=exc)

This would also require a different method of deriving the jwt key

@property
def cookie_jwt_key(self) -> str:
"""Signing key for MFA Cookie for this stage"""
return sha256(
f"{get_unique_identifier()}:{self.executor.current_stage.pk.hex}".encode("ascii")
).hexdigest()

I would be willing to create a PR for this if you want.

Describe alternatives that you've considered

I considered not using requiring MFA for my non critical apps and then using a single mfa stages for the apps that require it. But this isn't really desired.

Additional context

Possible related issues that I could find:

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestenhancement/confirmedEnhancements that will be implemented in the future

    Projects

    Status

    Todo

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions