----------------------
Released February 26, 2024
Among other changes, this continues the process of dis-entangling Flask-Security
from Flask-Login and may require some application changes due to backwards incompatible changes.
Features & Improvements
+++++++++++++++++++++++
- (:issue:`879`) Work with Flask[async]. view decorators and signals support async handlers.
- (:pr:`900`) CI support for python 3.12
- (:pr:`901`) Work with py_webauthn 2.0 (and only 2.0+)
- (:pr:`899`) Improve (and simplify) Two-Factor setup. See below for backwards compatability issues and new functionality.
- (:issue:`912`) Improve oauth debugging support. Handle next propagation in a more general way.
- (:pr:`877`) Make AnonymousUser (Flask-Login) optional and deprecated.
- (:pr:`906`) Remove undocumented and untested looking in session for possible 'next'
redirect location.
- (:pr:`881`) No longer rely on Flask-Login.unauthorized callback. See below for implications.
- (:issue:`904`) Changes to default unauthorized handler - remove use of referrer header (see below) and document precise behavior.
- (:pr:`927`) The authentication_token format has changed - adding per-token expiry time and future session ID.
Old tokens are still accepted.
Docs and Chores
+++++++++++++++
- (:pr:`889`) Improve method translations for unified signin and two factor. Remove support for Flask-Babelex.
- (:pr:`911`) Chore - stop setting all config as attributes. init_app(\*\*kwargs) can only
set forms, flags, and utility classes (see below for compatibility concerns).
- (:pr:`873`) Update Spanish and Italian translations. (gissimo)
- (:pr:`855`) Improve translations for two-factor method selection. (gissimo)
- (:pr:`866`) Improve German translations. (sr-verde)
- (:pr:`911`) Remove deprecation of AUTO_LOGIN_AFTER_CONFIRM - it has a reasonable use case.
- (:pr:`931`) Update message extraction - note that the CONFIRM_REGISTRATION message was changed to improve
readability.
Fixes
+++++
- (:issue:`845`) us-signin magic link should use fs_uniquifier (not email).
- (:issue:`893`) Improve open-redirect vulnerability mitigation. (see below)
- (:issue:`875`) user_datastore.create_user has side effects on mutable inputs. (NoRePercussions)
- (:pr:`878`) The long deprecated _unauthorized_callback/handler has been removed.
- (:issue:`884`) Oauth re-used POST_LOGIN_VIEW which caused confusion. See below for the new configuration and implications.
- (:pr:`908`) Improve CSRF documentation and testing. Fix bug where a CSRF failure could
return an HTML page even if the request was JSON.
- (:issue:`925`) Register with JSON and authentication token failed CSRF. (lilz-egoto)
- (:issue:`870`) Fix 2 issues with CSRF configuration.
- (:pr:`914`) It was possible that if :data:`SECURITY_EMAIL_VALIDATOR_ARGS` were set that
deliverability would be checked even for login.
Backwards Compatibility Concerns
+++++++++++++++++++++++++++++++++
- Passing in an AnonymousUser class as part of Security initialization has been removed.
- The never-public method _get_unauthorized_response has been removed.
- Social-Oauth - a new configuration variable :py:data:`SECURITY_POST_OAUTH_LOGIN_VIEW` was introduced
and it replaces :py:data:`SECURITY_POST_LOGIN_VIEW` in the oauthresponse logic when
:py:data:`SECURITY_REDIRECT_BEHAVIOR` == `"spa"`.
- Two-Factor setup. Prior to this release when setting up "SMS" the `/tf-setup` endpoint could
be POSTed to w/o a phone number, and then another POST could be made to set the phone number.
This has always been confusing and added complexity to the code. Now, if "SMS" is selected, the phone number
must be supplied (which has always been supported).
Other changes:
- The default two-factor-setup.html template now has a more generic `"Enter code to complete setup"` message.
- Make sure the `"disable"` option first.
- Adding any currently configured two-factor method on setup failure.
- The two_factor_verify template won't show the rescue form if it isn't set.
- A GET on /tf-validate now returns the two-factor-validate-form always - before
if the client was validating a new method, it would return the two-factor-setup-form
- After successfully disabling two-factor the client is redirected to :py:data:`SECURITY_TWO_FACTOR_POST_SETUP_VIEW`
rather than :py:data:`SECURITY_POST_LOGIN_VIEW`.
- Bring unauthenticated handling completely into Flask-Security:
Prior to this release, Flask-Security's :meth:`.Security.unauthn_handler` - called when
a request wasn't properly authenticated - handled JSON requests then delegated
form responses to Flask-Login's unauthenticated_callback. That logic has been moved
into Flask-Security and Flask-Login is configured to call back into Flask-Security's
handler. While the logic is very similar the following differences might be observed:
- Flask-Login's FORCE_HOST_FOR_REDIRECTS configuration isn't honored
- Flask-Login's USE_SESSION_FOR_NEXT configuration isn't honored
- The flashed message is SECURITY_MSG_UNAUTHENTICATED rather than SECURITY_MSG_LOGIN.
Furthermore, SECURITY_MSG_UNAUTHENTICATED was reworded to read better.
- Flask-Login uses `urlencode` to encode the `next` query param - which quotes the '/' character.
Werkzeug (which Flask-Security uses to build the URL) uses `quote`
which considers '/' a safe character and isn't encoded.
- The signal sent on an unauthenticated request has changed to :data:`user_unauthenticated`.
Flask-Login used to send a `user_unauthorized` signal.
- Flask-Security no longer configures anything related to Flask-Login's `fresh_login` logic.
This shouldn't be used - instead use Flask-Security's :meth:`flask_security.auth_required` decorator.
- Support for Flask-Babelex has been removed. Please convert to Flask-Babel.
- JSON error response has changed due to issue with WTForms form-level errors. When WTForms
introduced form-level errors they added it to the form.errors response using `None` as a key.
When serializing it, it would turn into "null". However, if there is more than one error
the default settings for JSON serialization in Flask attempt to sort the keys - which fails
with the `None` key. An issue has been filed with WTForms - and maybe it will be changed.
Flask-Security now changes any `None` key to `""`.
- The default unauthorized handler behavior has changed slightly and is now documented. The default
(:data:`SECURITY_UNAUTHORIZED_VIEW` == ``None``) has not changed (a default HTTP 403 response).
The precise behavior when :data:`SECURITY_UNAUTHORIZED_VIEW` is set was never documented.
The important change is that Flask-Security no longer ever looks at the request.referrer header and
will never redirect to it. If an application needs that, it can provide a callable that can return
that or any other header.
- Configuration variables (and other things) are no longer added as attributes on the Security instance.
For example `security.username_enable` no longer exists - this could be an issue in code or templates.
For templates, Flask places `config` in the Jinja context - so rather than using an attribute, use
`config["SECURITY_USERNAME_ENABLE"]` for the example above.
- Open Redirect mitigation. Release 4.1.0 had a fix for :issue:`486` involving a potential
open redirect. This was very low priority since the default configuration of Werkzeug (always
convert the Location header to absolute URL) rendered the vulnerability un-exploitable. The solution at that
time was to add an optional regex looking for these bizarre URLs that from a HTTP spec perspective
are relative, but various browsers would interpret as absolute. In Werkzeug release 2.1 the
default was changed so that the Location header was allowed to be a relative URL. This made the
open redirect vulnerability much more likely to be exploitable. More recently, additional bizarre
URLs were found, as documented in :issue:`893`. More work was done and a patch release 5.3.3
was published. This fix utilized changing the Werkzeug default back to absolute and an updated
regex. Comments and thoughts by gmanfuncky proposed a much better solution and that is in 5.4.
This implementation is independent of Werkzeug (and relative Location headers are again the default).
The entire regex option has been removed.
Instead, any user-supplied path used as a redirect is parsed and quoted.
Notes
++++++
- Historically, the **current_user** proxy (managed by Flask-Login) always pointed to a user object.
If the user wasn't authenticated, it pointed to an AnonymousUser object. With this release,
setting :py:data:`SECURITY_ANONYMOUS_USER_DISABLED` to `True` will force **current_user** to be set
to `None` if the requesting user isn't authenticated. It should be noted that this is in support
of a proposal by the Pallets team to remove AnonymousUser from Flask-Login - as well as deprecating
the `is_authenticated` property. The default behavior (`False`) should be the same as prior releases.
A new function `_fs_is_user_authenticated` is now part of the render_template context that
templates can use instead of `current_user.is_authenticated`.