You can write your own authentication backend to use raw passwords:
from django.contrib.auth import backends
from django.contrib.auth.models import User
class RawPasswordUser(User):
class Meta:
proxy = True
def set_password(self, raw_password):
# default implementation made a hash from raw_password,
# we don't want this
self.password = raw_password
def check_password(self, raw_password):
# same here, don't make hash out of raw_password
return self.password == raw_password
class ModelBackend(backends.ModelBackend):
def authenticate(self, username=None, password=None):
try:
user = RawPasswordUser.objects.get(username=username)
if user.check_password(password):
return user
except RawPasswordUser.DoesNotExist:
return None
def get_user(self, user_id):
try:
return RawPasswordUser.objects.get(pk=user_id)
except RawPasswordUser.DoesNotExist:
return None
In settings file:
AUTHENTICATION_BACKENDS = (
# ModelBackend from project_root/auth/backends.py
'auth.backends.ModelBackend',
)
Now when you authenticate
users in your views, you will get RawPasswordUser
instances. The same applies to login_required
decorator, request.user
will point to the proxy class.
See documentation for details.
For Django 1.5+ there is also an option to replace default User model with a custom one, but to keep existing users you will have to migrate them somehow, see this question.
Actually you won't be able to keep user passwords unchanged.
By default Django stores passwords in the following format:
algorithm$iterations$salt$hash
Which means:
You can't just regenerate passwords from hashes of originals, since you don't have the originals.
You also won't be able to generate the same hash on client-side, without knowing the salt. You could pass it to client-side, but salt is supposed to be a secret, so it's unwise to do it via unencrypted channel.
The easiest solution that I see is to keep current Django behaviour, as was suggested by Tadeck in the comments, add hashing to client-side and force users to change their passwords.
Well, it's not really a solution, because an attacker can intercept digested passwords and use them directly, but you mentioned it your question update. Since you don't care about security that much, you could also checkout public key encryption in JavaScript.
Another solution proposed by Tadeck is to use OAuth-like service, which might look somewhat like this:
def index(request):
access_token = request.REQUEST.get('token', None)
if not access_token:
return redirect('login')
# Custom authentication backend that accepts a token
# and searches for a user with that token in database.
user = authenticate(access_token)
if not user:
return redirect('login')
return render(...)
def auth(request):
''' This ajax-view has to be encrypted with SSL.'''
# Normal Django authentication.
user = authenticate(request.POST['username'], request.POST['password'])
# Authentication failed
if user is None:
return json.dumps({'error': '...'})
# generate, save and return token in json response
token = UserToken(user=user, value=generate_token())
# token.expires_at = datetime.now() + timedelta(days=1)
token.save()
return json.dumps({'token': token.value})
An attacked can still intercept an access token, but it's a bit better than intercepting password hash.