Tutorial 3: Authentication, Session, and Cookies
Platform-Based Programming (CSGE602022) — Organized by the Faculty of Computer Science Universitas Indonesia, Odd Semester 2024/2025
Learning Objectives
After completing this tutorial, students are expected to be able to:
- Understanding the basic concepts of authentication in web development.
- Understanding the role and function of cookies and sessions in web development.
- Understanding how cookies and sessions work in web development.
- Implementing cookies and sessions in a web project.
Introduction to HTTP
HTTP (HyperText Transfer Protocol) is a protocol used for communication between a client and a server. HTTP is stateless, meaning that each transaction/activity is considered a completely new transaction/activity. As such, no previous data is stored for the current transaction/activity.
Some basic concepts about HTTP:
-
Client/Server: The interaction occurs between a client and a server. The client is the party making the request, and the server is the party providing the response.
-
Stateless: Each activity (request/response) is independent, with no information retained from previous activities.
-
OSI Layer/Model: The Open Systems Interconnection (OSI) model describes the 7 layers used by computer systems to communicate over a network. The 7-layer OSI model consists of the Application Layer, Presentation Layer, Session Layer, Transport Layer, Network Layer, Data Link Layer, and Physical Layer.
-
Application Layer: In the OSI model mentioned above, websites operate at the application layer. However, the request/response process occurs at the transport layer, which generally uses the TCP protocol to determine how data is sent. The application layer doesn't concern itself with what the transport layer does (how data is sent, processed, etc) because it is solely focused on the request and response.
The other OSI layers will be covered in Computer Networks/Data Communication Networks courses. Feel free to look them up if you're curious. 😉
-
Client Actions Method: There are methods used by the client when making a request. Examples include: GET, POST, PUT, DELETE, etc. You can read more details here.
-
Server Status Code: These are status codes provided by the server in response to a request for a webpage. Examples include: 200 (OK), 404 (Page Not Found), 500 (Internal Server Error), etc. You can read more details here.
-
Headers: These are small pieces of information sent along with a request or response. This information is useful as additional data for processing the request/response. For example, in headers, there may be
content-type:json
, which means that the content being requested or sent is injson
format. Headers also store cookies data.
Introduction to Cookies & Session
All communications between the client and server is carried out through the HTTP protocol, where HTTP is a stateless protocol. This means that each state is independent and unrelated to the others. As a result, the client computer running the browser needs to create a TCP connection to the server every time a request is made.
Without a persistent connection between the client and server, software on either side (endpoints) cannot rely solely on the TCP connection for holding state or holding session state.
What is a holding state?
For example, if you want to access page A on a website that requires the user to be logged in. After logging into the website, you successfully access page A. If you then try to navigate to page B on the same website, without some form of holding state, you would be asked to log in again. This would happen every time you navigate to a different page, even though it’s the same website.
The process of telling the server "who" is logged in and storing this data is a form of client-server dialog and is the basis of a session — a semi-permanent exchange of information. Since HTTP is a stateless protocol, it is difficult to implement holding state, hence the need for techniques like Cookies and Sessions to solve this problem.
How to implement a holding state?
One of the most common methods for holding state is by using a session ID stored as a cookie on the client’s computer. A session ID acts as a token (a string of characters) that identifies a unique session in a web application. Instead of storing all types of user information as cookies on the client, such as username, name, and password, only the session ID is stored.
This session ID can then be mapped to a data structure on the web server side, where you can store all the information you need. This approach is much safer for storing user information compared to storing it in cookies. In this way, the information cannot be tampered with by the client or an insecure connection.
Additionally, this approach is more "appropriate" if there is a large amount of data to be stored, since cookies can only store a maximum of 4 KB of data. Imagine you have logged into a website/application and received a session ID (a session identifier). To achieve holding state in HTTP, which is stateless, the browser typically sends the session ID to the server with each request. That way, with each request, the server can respond (roughly) "Oh, this is the right person!" Then, the server retrieves the state information from memory or a database based on the session ID and returns the requested data.
The important distinction to remember is that cookie data is stored on the client side, while session data is usually stored on the server side. For more details on stateless, stateful, cookies, and sessions, you can read here.
Below is a summary table comparing cookies, session, and local storage.
Cookies | Local Storage | Sessions | |
---|---|---|---|
Capacity | 4 KB | 5 MB | 5 MB |
Browser Technology | HTML4/HTML5 | HTML5 | HTML5 |
Accessibility | All windows | All windows | Same tab |
Expiration | Set manually | Permanent | When tab closes |
Some video links that can expand your knowledge on this topic:
Pre-Tutorial Notes
Before you begin, and to help you follow Tutorial 3 effectively, we expect the following results from Tutorial 2:
- The directory structure of
mental-health-tracker
on your local machine should look like this:
mental-health-tracker
├── .github\workflows
│ └── deploy.yml
├── env
├── main
│ ├── migrations
│ │ ├── __init__.py
│ │ ├── 0001_initial.py
│ │ └── 0002_alter_moodentry_id.py
│ ├── templates
│ │ ├── create_mood_entry.html
│ │ └── main.html
│ ├── __init__.py
│ ├── admin.py
│ ├── apps.py
│ ├── forms.py
│ ├── models.py
│ ├── tests.py
│ ├── urls.py
│ └── views.py
├── mental_health_tracker
│ ├── __init__.py
│ ├── asgi.py
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
├── templates
│ └── base.html
├── .gitignore
├── manage.py
└── requirements.txt
- The
mental-health-tracker
repository structure on GitHub should look like this:
Tutorial: Implementing Function and Registration Forms
In the previous tutorial, we created a form for adding mood entries. How was it? Pretty easy, right? In this tutorial, we’ll make the main page (main
) restricted by creating user accounts. So, users who want to access the main page must first log in to gain access.
-
Firstly, activate the virtual environment in your terminal (Hint: Remember Tutorial 0!).
-
Open
views.py
in themain
subdirectory of your project. AddUserCreationForm
danmessages
imports at the top.from django.contrib.auth.forms import UserCreationForm
from django.contrib import messagesCode Explanation:
UserCreationForm
is a built-in form import that simplifies creating user registration forms in a web app. With this form, new users can easily register without needing to write custom code from scratch. -
Add the following
register
function toviews.py
. This function automatically generates the registration form and creates a user account when the form data is submitted.def register(request):
form = UserCreationForm()
if request.method == "POST":
form = UserCreationForm(request.POST)
if form.is_valid():
form.save()
messages.success(request, 'Your account has been successfully created!')
return redirect('main:login')
context = {'form':form}
return render(request, 'register.html', context)Code Explanation:
form = UserCreationForm(request.POST)
creates a newUserCreationForm
from the import by passing in user input fromrequest.POST
.form.is_valid()
validates the form input.form.save()
creates and saves the form data.messages.success(request, 'Your account has been successfully created!')
displays a success message to the user after registration.return redirect('main:show_main')
redirects the user to the main page after saving the form data.
-
Create a new HTML file named
register.html
in themain/templates
directory. You can use the following template:{% extends 'base.html' %} {% block meta %}
<title>Register</title>
{% endblock meta %} {% block content %}
<div class="login">
<h1>Register</h1>
<form method="POST">
{% csrf_token %}
<table>
{{ form.as_table }}
<tr>
<td></td>
<td><input type="submit" name="submit" value="Register" /></td>
</tr>
</table>
</form>
{% if messages %}
<ul>
{% for message in messages %}
<li>{{ message }}</li>
{% endfor %}
</ul>
{% endif %}
</div>
{% endblock content %}tipWe are using the
{{ form.as_table }}
tag to ease us in making the form into an already made table. For more information, you can read about it here -
Open
urls.py
in themain
subdirectory and import theregister
function.from main.views import register
-
Add a URL path to
urlpatterns
to access the imported function.urlpatterns = [
...
path('register/', register, name='register'),
]
Now that we’ve added the registration form and created the register
mechanism, in the next step, we'll create the login form so users can authenticate their accounts.
Tutorial: Implementing a Login Function
-
Reopen
views.py
in themain
subdirectory and add the importsauthenticate
,login
andAuthenticationForm
at the top.from django.contrib.auth.forms import UserCreationForm, AuthenticationForm
from django.contrib.auth import authenticate, loginCode Explanation:
At a glance, the
authenticate
andlogin
functions imported above are built-in Django functions used for authentication and login (if authentication is successful). For more details, refer to here. -
Add the
login_user
function below intoviews.py
. This function is used to authenticate users trying to log in.def login_user(request):
if request.method == 'POST':
form = AuthenticationForm(data=request.POST)
if form.is_valid():
user = form.get_user()
login(request, user)
return redirect('main:show_main')
else:
form = AuthenticationForm(request)
context = {'form': form}
return render(request, 'login.html', context)Code Explanation:
login(request, user)
is used to log in the user. If the user is valid, this function creates a session for the logged-in user.
-
Create a new HTML file named
login.html
in themain/templates
directory. Fill it with the following template:{% extends 'base.html' %}
{% block meta %}
<title>Login</title>
{% endblock meta %}
{% block content %}
<div class="login">
<h1>Login</h1>
<form method="POST" action="">
{% csrf_token %}
<table>
{{ form.as_table }}
<tr>
<td></td>
<td><input class="btn login_btn" type="submit" value="Login" /></td>
</tr>
</table>
</form>
{% if messages %}
<ul>
{% for message in messages %}
<li>{{ message }}</li>
{% endfor %}
</ul>
{% endif %} Don't have an account yet?
<a href="{% url 'main:register' %}">Register Now</a>
</div>
{% endblock content %} -
Open
urls.py
in themain
subdirectory and import the function you just created.from main.views import login_user
-
Add the URL path to
urlpatterns
to access the function.urlpatterns = [
...
path('login/', login_user, name='login'),
]
We have added the login form and created the login
mechanism. Next, we will implement the logout
mechanism and add a logout button to the main page.
Tutorial: Implementing a Logout Function
-
Reopen
views.py
in themain
subdirectory and add the followinglogout
import at the top.from django.contrib.auth import logout
-
Add the following function to
views.py
. This function implements the logout mechanism.def logout_user(request):
logout(request)
return redirect('main:login')Code Explanation:
logout(request)
is used to delete the session of the currently logged-in user.return redirect('main:login')
redirects the user to the login page in the Django application.
-
Open
main.html
file in themain/templates
directory and add the following code snippet after the hyperlink tag for "Add New Mood Entry."...
<a href="{% url 'main:logout' %}">
<button>Logout</button>
</a>
...Code Explanation:
{% url 'main:logout' %}
is used to dynamically link to the URL based onapp_name
andname
defined inurls.py
. In general, the syntax is{% url 'app_name:view_name' %}
:app_name
refers to the name of the app defined in theurls.py
file. If the app uses theapp_name
attribute inurls.py
, it will be used to refer to the app. Ifapp_name
is not defined, the app's folder name will be used as the app name.view_name
is the name of the URL defined via thename
parameter in thepath()
function inurls.py
. Code in urls.py
-
Open
urls.py
in themain
subdirectory and import thelogout_user
function you created earlier.from main.views import logout_user
-
Add the URL path to
urlpatterns
to access the function you imported earlier.urlpatterns = [
...
path('logout/', logout_user, name='logout'),
]
We have successfully implemented the logout
mechanism and completed the authentication system in this project.
Tutorial: Restricting Access to the Main Page
-
Reopen
views.py
in themain
subdirectory and add thelogin_required
import at the very top.from django.contrib.auth.decorators import login_required
Code Explanation:
The line
from django.contrib.auth.decorators import login_required
imports a decorator that requires users to log in before accessing a particular webpage. -
Add the code snippet
@login_required(login_url='/login')
above theshow_main
function so that the main page can only be accessed by authenticated (logged-in) users....
@login_required(login_url='/login')
def show_main(request):
...
Once you've restricted access to the main page, run your Django project with the command python manage.py runserver
and open http://localhost:8000/ in your favorite browser to see the result. Instead of showing the list of mood entries on the main page, it should redirect you to the login page.
Tutorial: Using Data from Cookies
Now, we will look at how to use cookies by adding last login
data and displaying it on the main page.
-
Firstly, log out if you are currently running your Django application.
-
Reopen
views.py
in themain
subdirectory. Add the imports forHttpResponseRedirect
,reverse
, anddatetime
at the very top.import datetime
from django.http import HttpResponseRedirect
from django.urls import reverse -
In the
login_user
function, we will add the functionality to set a cookie namedlast_login
to track when the user last logged in. Do this by replacing the code in theif form.is_valid()
block with the following snippet....
if form.is_valid():
user = form.get_user()
login(request, user)
response = HttpResponseRedirect(reverse("main:show_main"))
response.set_cookie('last_login', str(datetime.datetime.now()))
return response
...Code Explanation:
login(request, user)
handles logging the user in.response = HttpResponseRedirect(reverse("main:show_main"))
creates the response.response.set_cookie('last_login', str(datetime.datetime.now()))
creates alast_login
cookie and adds it to the response.
Pay attention to your code's indentation to ensure no dead code appears in the function.
-
In the
show_main
function, add the snippet'last_login': request.COOKIES['last_login']
to thecontext
variable. Here is an example of the updated code.context = {
'name': 'Pak Bepe',
'class': 'PBP D',
'npm': '2306123456',
'mood_entries': mood_entries,
'last_login': request.COOKIES['last_login'],
}Code Explanation:
'last_login': request.COOKIES['last_login']
adds thelast_login
cookie information to the response, which will be displayed on the web page. -
Modify the
logout_user
function to look like the following snippet.def logout_user(request):
logout(request)
response = HttpResponseRedirect(reverse('main:login'))
response.delete_cookie('last_login')
return responseCode Explanation:
response.delete_cookie('last_login')
deletes thelast_login
cookie when the user logs out. -
Open the
main.html
file and add the following snippet after the logout button to display thelast login
data....
<h5>Last login session: {{ last_login }}</h5>
... -
Refresh the login page (or run your Django project using the command
python manage.py runserver
if you haven't already) and try logging in. Yourlast login
data will appear on the main page. -
If you're using Google Chrome, you can view the
last_login
, cookie data by using the inspect element feature and opening the Application/Storage section. Click on the Cookies section, and you will see available cookies. Apart fromlast_login
, you can also seesessionid
andcsrftoken
. Below is an example of the view.
For Safari users, you can view cookies in the browser using Inspect element > Storage > Cookies
. If you can't find the inspect element option, enable it first via Safari > Preferences > Advanced
and check the option Show develop menu in menu bar
. Then, you can open the Inspect Element feature by right-clicking on the page and selecting Inspect Element, and navigate to Storage > Cookies
to see cookies like last_login
, sessionid
, and csrftoken
.
- If you log out and check the cookie history, the previously created cookie will be deleted and recreated when you log in again.
Before proceeding to the next tutorial, try creating an account on your website.
Tutorial: Connecting the MoodEntry
model to the User
model
You must follow the tutorial sequentially from the beginning before proceeding with the following section. If you do not follow the tutorial in order, we are not responsible for any errors outside the scope of the tutorial that may arise.
Lastly, we will link each MoodEntry
object created to the user who made it so that an authorized user only sees the mood entries they have created. Follow these steps:
-
Open
models.py
in themain
subdirectory and add the following code below the line that imports the model:...
from django.contrib.auth.models import User
... -
In the previously created
MoodEntry
model, add the following code:class MoodEntry(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
...Code Explanation:
The above code snippet connects a
MoodEntry
to aUser
through a relationship, where eachMoodEntry
is ensured to have an association with a user.
You will learn more about ForeignKey
in your Database course. More information on ForeignKey
in Django can be found here.
-
Reopen
views.py
in themain
subdirectory and modify the code in thecreate_mood_entry
function as follows:def create_mood_entry(request):
form = MoodEntryForm(request.POST or None)
if form.is_valid() and request.method == "POST":
mood_entry = form.save(commit=False)
mood_entry.user = request.user
mood_entry.save()
return redirect('main:show_main')
context = {'form': form}
return render(request, "create_mood_entry.html", context)
...Code Explanation:
The parameter
commit=False
prevents Django from immediately saving the object created from the form to the database. This allows us to modify the object before saving it. In this case, we will fill theuser
field with theUser
object from therequest.user
return value, indicating that the object belongs to the logged-in user. -
Change the value of
mood_entries
andcontext
in the functionshow_main
as follows.def show_main(request):
mood_entries = MoodEntry.objects.filter(user=request.user)
context = {
'name': request.user.username,
...
}
...Code Explanation:
- The above code displays the
Mood Entry
objects associated with the logged-in user. This is done by filtering all objects and only retrieving theMood Entry
where theuser
field is filled with theUser
object corresponding to the logged-in user. - The
request.user.username
code displays the logged-in user's username on the main page.
- The above code displays the
Before running the code in the next step, make sure there is at least one user in the database. This is necessary because in the next step, you will be prompted to assign a default value to the user
field for all existing rows. The model migration process will error if no user is previously recorded.
-
Save all changes and run the model migration with
python manage.py makemigrations
. -
You should encounter an error during the model migration. Select
1
to set a default value for theuser
field on all rows already created in the database. -
Type
1
again to assign the user with ID 1 (which we created earlier) to the existing model -
Run
python manage.py migrate
to apply the migration made in the previous step. -
Lastly, we need to ensure our project is ready for a production environtment. To do that, add another import statement in
settings.py
in themental_health_tracker
subdirectory.import os
Then, change the variable
DEBUG
insettings.py
into this.PRODUCTION = os.getenv("PRODUCTION", False)
DEBUG = not PRODUCTION
Run your Django project with the command python manage.py runserver
and open http://localhost:8000/ in your favorite browser to see the result. Try creating a new account and logging in with the newly created account. Observe the main page: the Mood Entry
created with the previous account will not be displayed on the page of the new account. This means you have successfully linked the Mood Entry
object to the User
who created it.
Conclusion
Congratulations! You've successfully completed Tutorial 3. 😄
After completing all the steps in this tutorial, you should now have a better understanding of using forms, authentication, sessions, and cookies in the Django framework.
-
After completing this tutorial, your web page should look like this:
- Login page at http://localhost:8000/login
- Main page at http://localhost:8000/ after successfully logging in:
-
By the end of this tutorial, your
main
subdirectory structure should look like this:mental-health-tracker
├── .github\workflows
│ └── deploy.yml
├── env
├── main
│ ├── migrations
│ │ ├── __init__.py
│ │ ├── 0001_initial.py
│ │ ├── 0002_alter_moodentry_id.py
│ │ └── 0003_moodentry_user.py
│ ├── templates
│ │ ├── create_mood_entry.html
│ │ ├── login.html
│ │ ├── main.html
│ │ └── register.html
│ ├── __init__.py
│ ├── admin.py
│ ├── apps.py
│ ├── forms.py
│ ├── models.py
│ ├── tests.py
│ ├── urls.py
│ └── views.py
├── mental_health_tracker
│ ├── __init__.py
│ ├── asgi.py
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
├── templates
│ └── base.html
├── .gitignore
├── manage.py
└── requirements.txt -
Before proceeding, ensure your local directory structure is correct. Next, run
add
,commit
, andpush
to update the GitHub repository. -
Run the following commands to
add
,commit
, andpush
:git add .
git commit -m "<commit_message>"
git push -u origin <main_branch>- Replace
<commit_message>
with your preferred message. Example:git commit -m "completed tutorial 3"
. - Replace
<main_branch>
with your main branch name. Example:git push -u origin main
orgit push -u origin master
.
- Replace
Contributors
- Abbilhaidar Farras Zulfikar
- Clarence Grady
- Reyhan Zada Virgiwibowo
- Fernando Valentino Sitinjak
- Alden Luthfi
- Vincent Suryakim (EN Translation)
Credits
This tutorial was developed based on PBP Ganjil 2024 written by the 2024 Platform-Based Programming Teaching Team. All tutorials and instructions included in this repository are designed so that students who are taking Platform-Based Programming courses can complete the tutorials during lab sessions.