In this section, we ll look into built-in tools that are used for user authentication
Make sure these two apps are listed in settings.py (They are usualy preloaded. If you load them manually, remember to migrate)
INSTALLED_APPS = [
'django.contrib.auth',
'django.contrib.contenttypes',
]
Never store passwords as plain text! Use django's built-in hashing algorithm (SHA - Secure Hashing algorithm).
Django's default hashing algorithm is PBKDF2
For more secure hashing algorithms, we can use state-of-art applications like bcrypt and argon2
pip install bcrypt pip install django[argon2]Inside of settings.py you can then pass in the list of PASSWORD_HASHERS to try in the order you want to try them.
PASSWORD_HASHERS = [ 'django.contrib.auth.hashers.Argon2PasswordHasher', 'django.contrib.auth.hashers.BCryptSHA256PasswordHasher', 'django.contrib.auth.hashers.BCryptPasswordHasher', 'django.contrib.auth.hashers.PBKDF2PasswordHasher', 'django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher', ]Django's built-in password validators
##Django's built in password validators AUTH_PASSWORD_VALIDATORS = [ { #Checks if password is similar to user name or other attributes 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', }, { #Checks for min length 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', #We can pass options to modify behaviour 'OPTION': {'min_length':9}, }, { #checks for weak password 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', }, { #Make sure password has numbers 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', }, ]Check documentation for list of password validation and its options
The media contents uploaded by the user(eg, profile pic) are stored under media directory. Make sure to add this directory in settings.py
# MEDIA INFORMATION: MEDIA_URL = '/media/' MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
To view these media through the admin interface, the following lines should be included in urls.py
from django.conf import settings
if settings.DEBUG:
from django.conf.urls.static import static
from django.contrib.staticfiles.urls import staticfiles_urlpatterns
# Serve static and media files from development server
urlpatterns += staticfiles_urlpatterns()
urlpatterns += static(settings.MEDIA_URL,
document_root=settings.MEDIA_ROOT)
There is a default User and Groups field in the admin interface which stores the information of the superusers etc.
To create a form to fill this field, use the User object in forms.py
#Import User from authorization models
from django.contrib.auth.models import User
#Attributes under User :
# username
# password
# email
# first_name
# last_name
class UserForm(forms.ModelForm):
password = forms.CharField(widget=forms.PasswordInput())
class Meta:
model = User
fields = ('username','email','password')
Most of the times, we may want to extend the attributes of the User object. We do this in models.py by creating a class that has-the fields of User object
from django.db import models
#Import User from authorization models
from django.contrib.auth.models import User
# Create your models here.
class UserProfileInfo(models.Model):
#Has-a relationship with User class!
user = models.OneToOneField(User,on_delete=models.CASCADE)
#additional attributes
portfolio_site = models.URLField(blank=True)
#upload_to='profile_pics' requires profile_pics to be a sub-dir under media
# pip install pillow to use this!
profile_pic = models.ImageField(upload_to='profile_pics',blank=True)
def __str__(self):
return self.user.username
In forms.py we add
from app5.models import UserProfileInfo
class UserProfileInfoForm(forms.ModelForm):
class Meta:
model = UserProfileInfo
fields = ('portfolio_site','profile_pic')
In views.py, there are several important things to note:
1) Passwords should be hashed and saved
2) In case of NULL in the fields with required=True, code will break when trying to save to DB. Therefore, use commit=False
3) Media files should be manually set
from django.shortcuts import render
from app5 import forms
# Create your views here.
def index(request):
return render(request,'index.html')
def register(request):
registered=False
if request.method == "POST":
user_form = forms.UserForm(request.POST)
profile_form = forms.UserProfileInfoForm(request.POST)
if user_form.is_valid() and profile_form.is_valid():
user = user_form.save()
#Hash the password. Without this, hashing algorithm is not applied and password is not saved
user.set_password(user.password)
user.save()
#commit = false does NOT save into database
#Without this, the code breaks because profile.user is NULL
profile = profile_form.save(commit=False)
#one-to-one relation with user. We dont apply this in the form field
profile.user = user
#Without this, the image is not saved in the directory nor shown in the admin
if 'profile_pic' in request.FILES:
profile.profile_pic = request.FILES['profile_pic']
profile.save()
registered = True
else:
print(user_form.errors,profile_form.errors)
else:
user_form = forms.UserForm()
profile_form = forms.UserProfileInfoForm()
return render(request,'registration.html',
{'registered':registered,
'user_form':user_form,
'profile_form':profile_form})
In settings.py add redirect url
#LOG IN URL (shoule match with urls.py) LOGIN_URL = '/app5/user_login/'Provide the validation in views.py
from django.contrib.auth import authenticate, login, logout
from django.http import HttpResponse, HttpResponseRedirect
#reverse(name) does the samething as {% url 'name' %}
from django.urls import reverse
# Upon decorating, this view requires the user to be logged in to render
from django.contrib.auth.decorators import login_required
def user_login(request): #renders login.html or redirects to homepage on login
if request.method == 'POST':
#Get the fields
username = request.POST.get('username') #login.html has a form with field names 'username'
password = request.POST.get('password')
#Authenticates the password for the user
#This return variable can be accessed across all HTML templates!
user = authenticate(username=username,password=password)
if user:
if user.is_active:
#The user remains logged in across the whole project
login(request,user)
#redirect to index.html
return HttpResponseRedirect(reverse('index'))
else:
return HttpResponse("Account not active")
else:
print(username, " tried to login with password ", password)
return HttpResponse("Invalid login details supplied!")
else:
return render(request,'login.html')
#This code breaks if there is no login
@login_required
def user_logout(request):
#The user is not logged out until this link is clicked
logout(request)
return HttpResponseRedirect(reverse('index'))
In urls.py
path("logout/",views.user_logout,name='logout'), path("user_login/",views.user_login,name='user_login'),
Using the authentication variable in HTML
<div class="jumbotron"> <!-- Tag user is available from views.py user = authenticate(username=username,password=password) --> {% if user.is_active %} <h1> Hello </h1> {% else %} <h1>Hello user!</h1> {% endif %} </div>Check documentation for all attributes