Can anyone show me how to make a proper login system for my django application?
I need help building a proper login system for my homework in my django class. I am supposed to build an app that users can login in token based authentication and can post a destination and review it, but I'm stuck in the login section. I've manage to build the register systems correctly, but now I need to help with logging in.
This is my login view I have. It keeps throwing a "NOT NULL constraint failed:" error so I have no idea what I'm doing wrong
def login(request: HttpRequest):
if request.method == "POST":
email = request.POST.get('email')
password_hash = request.POST.get('password')
hasher = hashlib.sha256()
hasher.update(bytes(password_hash, "UTF-8"))
password_hash = hasher.hexdigest()
token = ""
letters = string.ascii_lowercase
for _ in range(32):
token = "".join(random.choice(letters) for _ in range(32))
session_token = Session.objects.create(token=token)
response = HttpResponse("Cookie Set")
try:
user = User.objects.get(email=email, password_hash=password_hash)
if user.check_password(password_hash):
response = redirect("/")
response.set_cookie('token', token)
return response
else:
return HttpResponse("Invalid password")
except User.DoesNotExist:
return Http404({"Invalid email"})
return render(request, "destination/login.html")
Here are my models
from django.db import models
import hashlib
class User(models.Model):
id = models.BigAutoField(primary_key=True)
name = models.TextField()
email = models.EmailField(unique=True)
password_hash = models.TextField()
def check_password(self, hashed_password):
hasher = hashlib.sha256()
hasher.update(bytes(self.password_hash, "UTF-8"))
hashed_password = hasher.hexdigest()
return self.password_hash == hashed_password
class Session(models.Model):
id = models.BigAutoField(primary_key=True)
user = models.OneToOneField(User, on_delete=models.CASCADE)
token = models.TextField()
class Destination(models.Model):
id = models.BigAutoField(primary_key=True)
name = models.TextField()
review = models.TextField()
rating = models.PositiveIntegerField()
share_publicly = models.BooleanField(default=False)
user = models.ForeignKey("User", related_name="destinations", on_delete=models.CASCADE)
And here is my login form:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<form action="/login" method="POST">
<label for="">Email</label>
<input type="email" name="email" id="email">
<label for="">Password</label>
<input type="password" name="password" id="password">
<button type="submit">Login</button>
</form>
</body>
</html>
If anyone can see what's wrong or help me find a better way I'd be appreciative. Also I'm not allowed to use the Auth, Session, or Admin built in modules so please don't suggest it. Bonus if you show me how to only show destinations from the logged in user xD
You should first make sure that you have the user object before creating the session.
Also, you need to check the password correctly without re-hashing it unnecessarily.
I also can see that you're using the field name password
in your form, but in your view, you're trying to fetch it using password_hash
. This will lead to an empty password hash, so the login will fail.
you may update your login view like:
import hashlib
import random
import string
from django.shortcuts import render, redirect
from django.http import HttpResponse
from .models import User, Session
def login(request):
if request.method == "POST":
email = request.POST.get('email')
password = request.POST.get('password')
user = User.objects.filter(email=email).first()
if user and user.check_password(password):
token = ''.join(random.choice(string.ascii_letters) for _ in range(32))
session = Session.objects.create(user=user, token=token)
response = redirect("/")
response.set_cookie('token', token)
return response
return HttpResponse("Invalid email or password")
return render(request, "destination/login.html")
And update check_password
Method in the models:
class User(models.Model):
# Fields...
def check_password(self, password):
hasher = hashlib.sha256()
hasher.update(bytes(password, "UTF-8"))
return self.password_hash == hasher.hexdigest()
And finally to display only the destinations from the logged-in user, you'll need to fetch the user from the token stored in the cookie. Here is a view to display destinations:
def user_destinations(request):
token = request.COOKIES.get('token')
if token:
session = Session.objects.filter(token=token).first()
if session:
destinations = session.user.destinations.all()
return render(request, "destination/user_destinations.html", {"destinations": destinations})
return redirect("/login")
Well, a proper
login is relative. The framework itself already has a proper
login system out of the box, although its a session
one.There are many different kinds of authentication systems. Nevertheless, based on your question you are asking about authentication with Tokens
which is often used in API services. There are two standards, a plain Token Authentication
or a JWT - JSON Web Token Authentication
Usually, a developer does not waste time with such things. That is what libraries are for. Its always better to rely on such packages than implement for yourself, not just for agility but for security.
Relative to the User
model, you are working against the framework , thus hindering yourself. If you want a custom user model, then you should do it properly. By not using the model provided by the framework, you are missing a lot of built-in features such as password hashing.
With that said, here is a shallow example of token auth:
models.py
class Token(models.Model):
key = models.UUIDField(default=uuid.uuid4)
user = models.OneToOneField(User, on_delete=models.CASCADE)
expiration = models.DateTimeField()
@property
def is_expired(self):
return True if timezone.now() > self.expiration else False
def renew(self):
"""
Renew a token key and makes it valid for 3hours
its common for a obj to have a refresh_key in order
to do this process, which would require more logic.
"""
self.key = uuid.uuid4()
self.expiration = timezone.now() + datetime.timedelta(hours=3)
self.save()
def check_key(self, value):
try:
key = uuid.UUID(value)
assert key == self.key
return True
except Exception:
return False
views.py
def login(request):
if request.method == "POST":
email = request.POST.get("email")
password = request.POST.get("password")
token_key = request.COOKIES.get("token")
try:
# Fetch user and check password
user = User.objects.get(email=email)
if user.check_password(password):
# Verify token validity, renew if necessary
token = Token.objects.get(user=user)
if token.check_key(token_key):
if token.is_expired:
token.renew()
response = HttpResponse("Authentication Successfull")
response.set_cookie("token", token.key)
return response
else:
return HttpResponse("Invalid Token")
else:
return HttpResponse("Wrong Password")
except ObjectDoesNotExist:
return HttpResponse("User Not Found.")
else:
return render(request, "login.html")