User authentication for a Chrome Extension using a Django web app

Matt Brown
7 min readJul 16, 2024

--

Just outside of Telluride

Introduction

In this article, I show how to implement session-based authentication for a Chrome Extension using a Django web app.

While this article is specific to a Chrome Extension, the techniques are applicable to any application with decoupled front and backends.

Just want the code? Check out the GitHub Repo.

Video of the content below.

Authentication Overview

This section will give a quick overview of session and JWT authentication, and discuss how cookies are used to facilitate each method.

OAuth 2.0 is another form of authentication not discussed in this section

What are your authentication options?

Session-Based Authentication

Session-Based Authentication Process Flow

A session is created each time a user logs in. Here’s how it works:

  • A new Session object is created (row added to db table) and stored in the server-side Session table.
  • A sessionId cookie is stored on the client’s local machine.
  • For each request, the sessionId is included and checked against the server-side session table to authorize the user.

Pros:

  • Simplicity in implementation and management.
  • Server-side control over sessions.

Cons:

  • Requires server storage for session data.

JSON Web Tokens (JWT) Authentication

JWT authentication uses tokens created and stored client-side. Here’s the process:

  • When a user logs in, an access token and a refresh token are created and stored client-side (usually in cookies).
  • The access token is included in each request and is decrypted by Django using the secret key.
  • Access tokens have a shorter lifespan and are refreshed using the refresh token.

Pros:

  • Scalable as no session data is stored server-side.

Cons:

  • Complexity in token management and security.
  • Requires careful handling of token storage and expiration.

Understanding How Cookies Facilitate Each Method

Cookies are one method to store session IDs and JWTs client-side. Here are a few important details. The names of the parameters from Django settings.py are in parentheses.

  • HTTPOnly (SESSION_COOKIE_HTTPONLY): Set this value to True. This means cookies are only sent in Network requests and not available from JavaScript. It protects against a malicious code injection calling document.cookie to steal user auth data.
  • SameSite (SESSION_COOKIE_SAMESITE): The Lax value allows cookies to be sent with top-level navigations and GET requests initiated by third-party websites, but not with other cross-site requests.
  • Secure (SESSION_COOKIE_SECURE): Set this value to True in production. False means the cookie can be sent over both HTTP and HTTPS. True means the cookie will only be sent over secure HTTPS connections.
  • Expiration (SESSION_COOKIE_AGE): Defines how long the cookie lasts before it expires (in seconds).

Why choose Session-Based Authentication?

  • Simple Implementation: Just install a couple of libraries and add a few lines of code.
  • User-friendly: Users can log in once and be authenticated in the Chrome Extension (or any other front-end the app uses).
  • Secure: Less likely to mess up implementation because of it’s simplicity. Sessions are stored server-side so revoking access is trivial.

I recommend you use session-based auth unless you have a good reason to choose another. If you do need JWT, check out this post which details JWT in Django.

How to Implementation Session Auth (Django)

Okay now let’s get into implementation details. We will start from scratch with a basic Django app.

Create a Django Web App

For this tutorial, it’s assumed you know the basics of developing a Django application. If you’re new to Django, check out their documentation.

Here are the basic commands for building a Django app.

virtualenv ./env --python=python3.11
source ./env/bin/activate
pip install django
django-admin startproject session_chrome_app
cd session_chrome_app
python manage.py startapp core
python manage.py migrate
python manage.py createsuperuser

You should have a Django app that you can run with python manage.py runserver.

Install the necessary libraries

pip install dj-rest-auth django-allauth

django-allauth is only necessary if you don’t have an existing app with a login view. It is just used to provide UI views.

Update settings

Add the necessary apps toINSTALLED_APPS in settings.py

# settings.py

INSTALLED_APPS = [
...
# The following apps are required:
"django.contrib.auth",
"django.contrib.messages",
"rest_framework.authtoken",
"dj_rest_auth",
"dj_rest_auth.registration",
"allauth",
"allauth.account",
]

Add django-allauth middleware.

MIDDLEWARE = [
...
"allauth.account.middleware.AccountMiddleware",
]

You can add the code below at the bottom of settings.py.

NOTE: You will need to update CSRF_TRUSTED_ORIGINS to be the location of your Chrome Extension. This is automatically assigned when you load the extension. We will come back to this at the end of the tutorial.

# settings.py
SESSION_COOKIE_SECURE = False
SESSION_COOKIE_HTTPONLY = True
SESSION_COOKIE_SAMESITE = 'Lax'
SESSION_COOKIE_AGE = 10
CSRF_COOKIE_SECURE = True

REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.SessionAuthentication',
),
}


AUTHENTICATION_BACKENDS = [
# Needed to login by username in Django admin, regardless of `allauth`
'django.contrib.auth.backends.ModelBackend',

# `allauth` specific authentication methods, such as login by email
'allauth.account.auth_backends.AuthenticationBackend',
]

ACCOUNT_AUTHENTICATED_LOGIN_REDIRECTS=False
LOGIN_REDIRECT_URL = '/'

CSRF_TRUSTED_ORIGINS = [
'chrome-extension://pnchahhkakickfihmeklhjgeacjhcdii'
]

Run python manage.py migrate

Add a home view

Create a basic view decorated with @login_required. Makes it easier to test the auth system.

# views.py

from django.shortcuts import render
from django.http import HttpResponse
from django.conf import settings
from django.contrib.auth.decorators import login_required

# Create your views here.
@login_required
def home(request):
return HttpResponse("Hello, Django!")

Add URL paths for home, django-allauth and dj-rest-auth

session_chrome_app/urls.py

# session_chome_app/urls.py

from django.contrib import admin
from django.urls import path
from django.urls.conf import include
from core.views import home

urlpatterns = [
path("admin/", admin.site.urls),
path("", home, name="home"),
path('dj-rest-auth/', include('dj_rest_auth.urls')),

path("accounts/", include("allauth.urls")),
]

Setup services for Chrome Extension

We’ll call the dj-rest-auth/user view from the Chrome Extension to authorize users.

We now have a functioning Django app that can be used to authenticate a Chrome Extension (or any other front-end application).

Verify that sessionId cookies are being created and stored properly

Each time the user logs in, a sessionId cookie will be stored on their local machine. The cookie will be sent with each network request. Let’s take a look.

Below you can see the sessionid cookie. You can verify the settings we created earlier HttpOnly, Secure and SameSite.

Network Settings

You can see that a sessionid cookie is sent in the request

How to get to the Network settings

  1. Go to localhost:8000 and log in with the superuser you created earlier.
  2. Open dev tools -> Network
  3. Reload the page (You should be on the home page at this point)
  4. Click on the request that was just made (should say localhostor 127.0.0.1)

Step-by-Step Implementation (Chrome Extension)

You can get the Chrome Extension from the GitHub repo. Load the chrome_extension folder from this repo to chrome://extensions. Below is an explanation of how it works.

Update settings.py to be the unique identifier of the extension

You can get the ID from chrome://extensions

Then update the settings in your Django app to the correct identifier.

# settings.py

CSRF_TRUSTED_ORIGINS = [
# NOTE: You will need to update with the unique identifier of your chrome extension
'chrome-extension://mgcmoolmkgaihncgljjammoondcjibnl'
]

Implementing User Authorization

dj-rest-auth/user can be used to determine if a user is currently logged in. It looks up the sessionid from the server-side session table. This is the only auth service called from this sample extension because we are handling the rest of the authentication from the Django app.

Ensure you include credentials: ‘include’. It tells the browser to include the HttpOnly cookies including sessionid in the request.

verifyToken() is responsible for authorizing the user.

In this function, if the response is ok, then we return the response as JSON. Else we reject() the promise and the next function, activateApp() will handle the rejection.

function verifyToken() {
let url = baseUrl;

return new Promise((resolve, reject) => {

fetch(`${url}/dj-rest-auth/user/`, {
method: 'GET', // Use GET to check session validity
headers: {
'Content-Type': 'application/json'
},
// Ensures that the sessionid cookie is included in the request
credentials: 'include'

})
.then(response => {
console.log(response)
if (response.ok) {
chrome.action.setBadgeText({ text: 'Verif' });
chrome.action.setBadgeBackgroundColor({ color: 'green' }); // Optional: Set the badge color
console.log('Token is valid');
resolve(response.json()); // Token is valid
} else {
console.log('Session id is invalid or session expired');
reject();
}
})

.catch(error => {
console.error('Error:', error);
reject(error);
});
});
}

Using verifyToken()

Here’s a demonstration of how to use verifyToken(). For this application, we call it every time the user clicks the app icon. It would be called anytime the user makes a request that requires authentication.

In activateApp(), we call verifyToken() and check that it returns data.email, a field that will only be returned if the user is currently logged in. Otherwise, we open the log in screen in a new tab.

function activateApp() {
verifyToken().then(data => {
if(typeof(data.email) && data.email) {
startApp();
}
else {
throw new Error('User not logged in');

}
}).catch(error => {
console.log('Error:', error);
chrome.action.setBadgeText({ text: 'Error' });
chrome.action.setBadgeBackgroundColor({ color: 'red' }); // Optional: Set the badge color

let loginUrl = `${baseUrl}/accounts/login/`;
chrome.tabs.create({ url: loginUrl });
});
}

Congratulations!

You’ve successfully set up Session-based authentication for a Chrome Extension. For the full code, visit our GitHub Repo. Feel free to share your feedback, ask questions, or suggest improvements in the comments below. Happy coding!

Here’s a quick demo of the app.

--

--

Matt Brown

I'm Matt. A freelance software developer. Contact me if you'd like to work together! matt@gosolucia.com www.MatthewLBrown.com