-------------
Released September 26, 2019
**There are several default behavior changes that might break existing applications.
Most have configuration variables that restore prior behavior**.
**If you use Authentication Tokens (rather than session cookies) you MUST make a (small) change.
Please see below for details.**
- (:pr:`120`) Native support for Permissions as part of Roles. Endpoints can be
protected via permissions that are evaluated based on role(s) that the user has.
- (:issue:`126`, :issue:`93`, :issue:`96`) Revamp entire CSRF handling. This adds support for Single Page Applications
and having CSRF protection for browser(session) authentication but ignored for
token based authentication. Add extensive documentation about all the options.
- (:issue:`156`) Token authentication is slow. Please see below for details on how to enable a new, fast implementation.
- (:issue:`130`) Enable applications to provide their own :meth:`.render_json` method so that they can create
unified API responses.
- (:issue:`121`) Unauthorized callback not quite right. Split into 2 different callbacks - one for
unauthorized and one for unauthenticated. Made default unauthenticated handler use Flask-Login's unauthenticated
method to make everything uniform. Extensive documentation added. `.Security.unauthorized_callback` has been deprecated.
- (:pr:`120`) Add complete User and Role model mixins that support all features. Modify tests and Quickstart documentation
to show how to use these. Please see :ref:`responsetopic` for details.
- Improve documentation for :meth:`.UserDatastore.create_user` to make clear that hashed password
should be passed in.
- Improve documentation for :class:`.UserDatastore` and :func:`.verify_and_update_password`
to make clear that caller must commit changes to DB if using a session based datastore.
- (:issue:`122`) Clarify when to use ``confirm_register_form`` rather than ``register_form``.
- Fix bug in 2FA that didn't commit DB after using `verify_and_update_password`.
- Fix bug(s) in UserDatastore where changes to user ``active`` flag weren't being added to DB.
- (:issue:`127`) JSON response was failing due to LazyStrings in error response.
- (:issue:`117`) Making a user inactive should stop all access immediately.
- (:issue:`134`) Confirmation token can no longer be reused. Added
*SECURITY_AUTO_LOGIN_AFTER_CONFIRM* option for applications that don't want the user
to be automatically logged in after confirmation (defaults to True - existing behavior).
- (:issue:`159`) The ``/register`` endpoint returned the Authentication Token even though
confirmation was required. This was a huge security hole - it has been fixed.
- (:issue:`160`) The 2FA totp_secret would be regenerated upon submission, making QRCode not work. (malware-watch)
- (:issue:`166`) `default_render_json` uses ``flask.make_response`` and forces the Content-Type to JSON for generating the response (koekie)
- (:issue:`166`) *SECURITY_MSG_UNAUTHENTICATED* added to the configuration.
- (:pr:`168`) When using the auth_required or auth_token_required decorators, the token
would be verified twice, and the DB would be queried twice for the user. Given how slow
token verification is - this was a significant issue. That has been fixed.
- (:issue:`84`) The :func:`.anonymous_user_required` was not JSON friendly - always
performing a redirect. Now, if the request 'wants' a JSON response - it will receive a 400 with an error
message defined by *SECURITY_MSG_ANONYMOUS_USER_REQUIRED*.
- (:pr:`145`) Improve 2FA templates to that they can be localized. (taavie)
- (:issue:`173`) *SECURITY_UNAUTHORIZED_VIEW* didn't accept a url (just an endpoint). All other view
configurations did. That has been fixed.
Possible compatibility issues
+++++++++++++++++++++++++++++
- (:pr:`164`) In prior releases, the Authentication Token was returned as part of the JSON response to each
successful call to `/login`, `/change`, or `/reset/{token}` API call. This is not a great idea since
for browser-based UIs that used JSON request/response, and used session based authentication - they would
be sent this token - even though it was likely ignored. Since these tokens by default have no expiration time
this exposed a needless security hole. The new default behavior is to ONLY return the Authentication Token from those APIs
if the query param ``include_auth_token`` is added to the request. Prior behavior can be restored by setting
the *SECURITY_BACKWARDS_COMPAT_AUTH_TOKEN* configuration variable.
- (:pr:`120`) :class:`.RoleMixin` now has a method :meth:`.get_permissions` which is called as part
each request to add Permissions to the authenticated user. It checks if the RoleModel
has a property ``permissions`` and assumes it is a comma separated string of permissions.
If your model already has such a property this will likely fail. You need to override :meth:`.get_permissions`
and simply return an emtpy set.
- (:issue:`121`) Changes the default (failure) behavior for views protected with auth_required, token_auth_required,
or http_auth_required. Before, a 401 was returned with some stock html. Now, Flask-Login.unauthorized() is
called (the same as login_required does) - which by default redirects to a login page/view. If you had provided your own
`.Security.unauthorized_callback` there are no changes - that will still be called first. The old default
behavior can be restored by setting *SECURITY_BACKWARDS_COMPAT_UNAUTHN* to True. Please see :ref:`responsetopic` for details.
- (:issue:`127`) Fix for LazyStrings in json error response. The fix for this has Flask-Security registering
its own JsonEncoder on its blueprint. If you registered your own JsonEncoder for your app - it will no
longer be called when serializing responses to Flask-Security endpoints. You can register your JsonEncoder
on Flask-Security's blueprint by sending it as `json_encoder_cls` as part of initialization. Be aware that your
JsonEncoder needs to handle LazyStrings (see speaklater).
- (:issue:`84`) Prior to this fix - anytime the decorator :func:`.anonymous_user_required` failed, it caused a redirect to
the post_login_view. Now, if the caller wanted a JSON response, it will return a 400.
- (:issue:`156`) Faster Authentication Token introduced the following non-backwards compatible behavior change:
* Since the old Authentication Token algorithm used the (hashed) user's password, those tokens would be invalidated
whenever the user changed their password. This is not likely to be what most users expect. Since the new
Authentication Token algorithm doesn't refer to the user's password, changing the user's password won't invalidate
outstanding Authentication Tokens. The method :meth:`.UserDatastore.set_uniquifier` can be used by an administrator
to change a user's ``fs_uniquifier`` - but nothing the user themselves can do to invalidate their Authentication Tokens.
Setting the *SECURITY_BACKWARDS_COMPAT_AUTH_TOKEN_INVALIDATE* configuration variable will cause the user's ``fs_uniquifier`` to
be changed when they change their password, thus restoring prior behavior.
New fast authentication token implementation
++++++++++++++++++++++++++++++++++++++++++++
Current auth tokens are slow because they use the user's password (hashed) as a uniquifier (the
user id isn't really enough since it might be reused). This requires checking the (hashed) password against
what is in the token on EVERY request - however hashing is (on purpose) slow. So this can add almost a whole second
to every request.
To solve this, a new attribute in the User model was added - ``fs_uniquifier``. If this is present in your
User model, then it will be used instead of the password for ensuring the token corresponds to the correct user.
This is very fast. If that attribute is NOT present - then the behavior falls back to the existing (slow) method.
DB Migration
~~~~~~~~~~~~
To use the new UserModel mixins or to add the column ``user.fs_uniquifier`` to speed up token
authentication, a schema AND data migration needs to happen. If you are using Alembic the schema migration is
easy - but you need to add ``fs_uniquifier`` values to all your existing data. You can
add code like this to your migrations::update method::
be sure to MODIFY this line to make nullable=True:
op.add_column('user', sa.Column('fs_uniquifier', sa.String(length=64), nullable=True))
update existing rows with unique fs_uniquifier
import uuid
user_table = sa.Table('user', sa.MetaData(), sa.Column('id', sa.Integer, primary_key=True),
sa.Column('fs_uniquifier', sa.String))
conn = op.get_bind()
for row in conn.execute(sa.select([user_table.c.id])):
conn.execute(user_table.update().values(fs_uniquifier=uuid.uuid4().hex).where(user_table.c.id == row['id']))
finally - set nullable to false
op.alter_column('user', 'fs_uniquifier', nullable=False)
for MySQL the previous line has to be replaced with...
op.alter_column('user', 'fs_uniquifier', existing_type=sa.String(length=64), nullable=False)