# Code Review: Token authentication by querying Django users by password

**URL:** <https://forum.djangoproject.com/t/code-review-token-authentication-by-querying-django-users-by-password/41592>\
**Category:** Forms & APIs\
**Created:** [June 26, 2025, 8:43pm UTC](https://forum.djangoproject.com/t/code-review-token-authentication-by-querying-django-users-by-password/41592 "2025-06-26T20:43:41Z")\
**Posts on this page:** 1\
**Page:** 1

<div class="post-metadata">

**Author:** ![doom](https://avatars.discourse-cdn.com/v4/letter/d/a587f6/32.png) [@doom](https://forum.djangoproject.com/u/doom)\
**Post date:** [June 26, 2025, 8:43pm UTC](https://forum.djangoproject.com/t/code-review-token-authentication-by-querying-django-users-by-password/41592/1 "2025-06-26T20:43:41Z")

</div>

I have a Django project that I want to extend with an API which meets the following requirements:

- The API requires authentication via Bearer token.
- The superuser should be able to create tokens.
- Token should be created by the click of a button.
- They must be system-generated.
- And they should be displayed to the superuser only once.

I want to ask for feedback about my implementation, because I’m doing some shenanigans with authentication and password storing. Specifically adding the password as a one-time attribute to the user object, and hashing the password with a static (non-random) salt. I believe this approach is good-enough for its use-case (without changing the use-case), but I’d like to be sure I’m not making it more complex than it needs to be, or missing an important bit.

The way I implemented this is a follows.

I created a new model `Account` that has a 1:1 relation to a `User`.

```auto
# api/models.py
from django.contrib.auth.models import User

class Account(models.Model):
    user = OneToOneField(User, on_delete=models.CASCADE)
    ...

```

If the superuser creates a new token via the click of a button, behind the scenes it actually creates a new `Account` through a form.

```auto
# api/forms.py
from api.models import Account

class AccountForm(ModelForm):
    ...

    class Meta:
        model = Account

```

To display the token to the user, I’m actually doing some shenanigs on the model, that I did not show you earlier. During the `Account(...).save()` phase I am checking the internal state if the new Account objects is being added and that it does not exist in the database (`if self._state.adding and not self._state.db`). If this is the case, I am generating a random password `get_random_string(32)` and assigning it to a new property `self.secret`. This property only exists temporarily. Then I am creating a new `User` object and set its password to `self.secret`. Furthermore, I am salting the password with a static string that must stay the same for every `User` object that is created through an `Account`object. I’m explaining this a little later. For now, this is the extended `models.py`:

```auto
# api/models.py
from django.contrib.auth.models import User

class Account(models.Model):
    user = OneToOneField(User, on_delete=models.CASCADE)
    
    def save(self, *args, **kwargs):
        # TODO prevent updating secret_partial
        if self._state.adding and not self._state.db:
            self.secret = get_random_string(32)
            user = User(username=uuid.uuid4())
            user.password = make_password(self.secret, salt="<REDACTED>")
            user.save()
            self.secret_partial = self.secret[-3:]
            self.user = user
        super().save(*args, **kwargs)

```

And below is the view which puts it all together. After checking that the form was submitted and valid, I am saving the form, which returns the Account object with the temporary `create` property (`account = form.save()`). I then render the response including the randomly created `secret`. In no other cases the `secret` exposed to the `Account`object. The user now must copy and store the secret before he leaves the current page.

```auto
# api/views.py
from api.forms import AccountForm
from api.models import Account

def account_create(request):
    form = AccountForm(request.POST or None)

    if request.method == "POST" and form.is_valid():       
        account = form.save()        
        return render(
            request,
            "api/partials/account_create_response.html",
            { "secret": account.secret },
        )

    ...

```

Back to the salt. Why did I chose a static salt, instead of a randomly created one? The reason is the Bearer token authentication. Bearer authentication does not require nor support user ids. Therefore, if a client authenticates with the token only, then I need to be able to find a user with that password, or fail the authentication. And I believe that in order to do that, not only the password, but also the salt must be known so I can generate the exact same hash to run the query.

```auto
# api/api.py
from ninja.security import HttpBearer
from django.contrib.auth.hashers import make_password
from .models import Account

class GlobalAuth(HttpBearer):
    def authenticate(self, request, token):
        secret = base64.b64decode(token).decode('utf-8')
        hashed_password = make_password(secret, salt="<REDACTED>")
        try:
            account = Account.objects.get(user__password=hashed_password)
            return account.pk
        except Account.DoesNotExist:
            return

api = NinjaAPI(auth=GlobalAuth())

```

Thank you!
