django-rest-knox icon indicating copy to clipboard operation
django-rest-knox copied to clipboard

Migrating from rest_framework.TokenAuthentication without logging users out

Open oaosman84 opened this issue 4 years ago • 3 comments

Is there a recommended way to migrate to knox from DRF's TokenAuthentication—without logging users out?

If I swap in knox instead of DRF, then all users need to re-login (since their old token isn't valid). I can't seem to get both systems working simultaneously, because knox.TokenAuthentication raises an exception (instead of returning None) if the the token is invalid, which causes Django not to try subsequent authenticators.

Ideally, I'd like a way to try knox.TokenAuthentication, then if it fails but the token works in DRF, create a knox token for that user (then at some point in the future, completely remove DRF tokens).

oaosman84 avatar Aug 21 '20 17:08 oaosman84

I suppose if you're running say a redux store you could check the token in from the browser storage/store and see if in the DRF table. If it is remove it and send back a new token from the knox table. Then slowly phase that out until the DRF table is empty or a majority of active users have switched.

joelawm avatar Sep 23 '20 05:09 joelawm

Something like this would work:

from django.db import migrations
from knox import crypto
from knox.settings import CONSTANTS


def convert_tokens(apps, schema_editor):
    Token = apps.get_model("authtoken", "token")
    AuthToken = apps.get_model("knox", "authtoken")
    db_alias = schema_editor.connection.alias

    for token in Token.objects.using(db_alias).all():
        digest = crypto.hash_token(token.key)
        # bypass custom manager
        AuthToken.objects.using(db_alias).bulk_create(
            [
                AuthToken(
                    digest=digest,
                    token_key=token.key[: CONSTANTS.TOKEN_KEY_LENGTH],
                    user_id=token.user_id,
                    expiry=None,  # or whatever logic you want
                )
            ]
        )
        # bypass auto_now_add restriction
        AuthToken.objects.using(db_alias).filter(digest=digest).update(
            created=token.created
        )

    Token.objects.using(db_alias).all().delete()


class Migration(migrations.Migration):

    dependencies = [
        ("knox", "0008_remove_authtoken_salt"),
    ]

    operations = [migrations.RunPython(convert_tokens)]

(keep in mind this is for the latest unreleased version of knox where the salt has been removed from AuthToken)

Remove "rest_framework.authentication.TokenAuthentication" from your views and default authentication, but keep both "rest_framework.authtoken" and "knox" in INSTALLED_APPS until the migration has run everywhere.

jonathan-golorry avatar Feb 15 '21 23:02 jonathan-golorry

@jonathan-golorry just created a PR with a "safe" version of your migration

gawry avatar Jan 11 '23 18:01 gawry