Be logged in mutiple sites

I have an app that uses Django (and Rest Framework) as the back end but also a small website that serves a couple of pages. Then I have a Vue app that is actually the main application. They would site on the same domain but the Vue app will be on a sub domain. How I can I be logged in on both sides so for instance if I log in on the Django side then I am also logged in on the Vue side, so if a user visits the Django side he can see a button for the dashboard.

I am using JWT for logging in

Nobody seems to be able to answer this question.

When you are referring to your Vue app, is the server-side of that your same Django project using the same database?

If so, then you’ll want to use session-based Authentication for DRF.

From the docs:

Session authentication is appropriate for AJAX clients that are running in the same session context as your website.

If not; if Vue is connecting to a completely separate Django instance, or even a non-Django back-end, you’ll want to set up some kind of coordinated single sign-on solution such as CAS.

The app works like this:
I have a Django back end that has Django Rest Framework installed. I have an app called pages that serve a bunch of pages on the Django side of things. Just home, about terms etc. I can log in on the Django login page of the pages app and it works and I am logged in. Once logged in I am redirected to the Vue app that will sit on a sub domain. I am not there yet where I can combine Vue with the standard pages of my pages app, which is what I will try next. But until I am there I have Vue on a sub domain and I cannot get session authentication to work because I cannot share cookies between domains and sub domains and so I opted for JWT and for the last two days been struggling to find a way to share the token between my Django pages app and my Vue app. Appreciate your help

I don’t know by what you mean when you say

Vue runs in the browser - I am only concerned with what Vue is connecting to on the backend.
(It doesn’t matter what you run on the front-end - whether it’s Vue, React, Angular, etc - the back-end issues are all the same.)

Vue is a stand alone application and really has nothing to do with Django. I can run it on a separate domain and it will still work because it gets the data from the DRF end points.

So in production Vue will be on the sub domain app.somewebsite.com and the Django app will sit on somewebsite.com

The Django app has a pages app that give it its pages and where some templates live.

On my localhost the Django app and Vue app run on different ports. Django is on localhost:8000 and the Vue app is on localhost:8080

So at the moment I can log in on the Django side and it works fine and I use JWT for that.

I can also log in on the Vue side using axios and DRF

But I cannot log in on one side (Vue OR Django) and then also be logged in on the other side and that is what I am trying to do.

Forget Vue here for the moment. I’m still trying to understand what you’re using on the server. The client is irrelevent at the moment.

Without mentioning “Vue” at all, are you saying that you have two different Django installations - one standard and one DRF, and you want to use the same authentication mechanism on both?

If that’s not an accurate summary, please correct me regarding what is running on the two server instances.

Ok forget about Vue. I have one Django installation using DRF to give me an API. I have a pages app that is part of the Django installation and I use that to serve up some pages.

I can log in on the login page that is part of the pages app and it works and I can navigate the pages while being logged in. No problem there.

Ok, that’s half of it. What about the other application that your JavaScript front end connects to?

There is no other application. My Vue front end connects to the One Django installation through DRF but I have to log into it otherwise I cannot access the end points because they will tell me unauthorized because the Token is not in the headers and that is where my problems begin

Ok, that was what I was trying to determine.

You loading a Vue app from a different subdomain is mostly irrelevent to this. All authentication is occurring in your one Django project.

So in order for both sides (traditional Django and DRF) to share a common login, you either need to use session-based authentication on both sides, or write your own authentication backend(s) to use a common token structure.

Yes I have both (Token and Session) and a custom back end for logging in with email or username. How to make me being logged in at both Django and DRF is what I am struggling with and to date nobody is able to assist me. Wish I was more experienced but I’m not and this has been a struggle for two days now

So I wil repeat my previous answer. Use session-based authentication for both.

As stated by Ken, you can use session authentication for both the django and drf parts.

But, as your vue frontend is held by another domain than the backend, you have to correctly configure CORS so that the session cookie is sent to your backend (at somewebsite.com) when request is fired from script in app.somewebsite.com.

See GitHub - adamchainz/django-cors-headers: Django app for handling the server headers required for Cross-Origin Resource Sharing (CORS)

At least, you will need to set the following settings:

CORS_ALLOWED_ORIGINS = ["https://app.somewebsite.com"]
CORS_ALLOW_CREDENTIALS = True
SESSION_COOKIE_SAMESITE = "None"
SESSION_COOKIE_SECURE = True

You also need to ensure that requests done in the Vue app have withCredentials set to true (e.g. see vue.js - SET WithCredentials globally with axios on VueJS - Stack Overflow)

Pay attention to your CORS configuration, allowing unwanted hosts (e.g. by setting CORS_ALLOW_ALL_ORIGINS to True) would expose your site to security issues

1 Like

Apologies maybe I am not explaining my problem correctly. I have all those settings set in the django app settings. I just would like to understand how it works so please bear with me. I am inexperienced when it comes to this and trying to learn.

When I log in using session authentication it creates a session cookie that is stored in the browser. That cookie is the session id and the Django uses it to validate the user making requests. But all (well most) of my DRF endpoints function on the ID of the user.

My problem comes in with the fact that Vue has no access to that cookie so I cannot use that cookie to get the data of the user currently logged in because I don’t have access to the user data because I don’t have the session id, which sits in the session cookie that is not available to my Vue app sitting on a different domain, because as far as I know cookies are not available across domains or sub domains.

I mean the Vue app must have some way to identify itself otherwise the request can come from anywhere. How does the Vue app prove that it’s the logged in user using it to make requests and this is what I am struggling with.

So for example if I log in on the the login page of the Django pages app then I get redirected to my Vue application and there I would like to get the logged in users data and put it in my store (Pinia store). So do I need to send the cookie, or session id to the Vue app or what?

Hope I am making sense here

Thanks for the help I really appreciate it.

You change your DRF application to use the session-based authentication as referenced above.

Where your Vue app is loaded from is irrelevent, provided you have the CORS issues addressed. Vue shouldn’t need to “see” the cookie, because it should be submitted “automatically” along with every AJAX request.

Ok so I don’t even have to be on the same browser. And if I make a request to some endpoint let’s say “api/v1/users/1” then the session cookie would be attached to my request because the request comes from an ip or domain listed in the CORS header settings?

Not working for me because making a request from the same browser to a specific endpoint so I can get the user data (api/v1/users/1 in this case) gives me a 403 response and "Authentication credentials were not provided. I am currently logged in as the superuser on the Django app. So no cookie is getting attached to my request from my Vue app and so for all endpoints I am denied access.

Here is my entire settings file:

from pathlib import Path
import os
from dotenv import load_dotenv
from datetime import timedelta

load_dotenv()

# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent


# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/3.2/howto/deployment/checklist/

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'django-insecure-lvr_@mcihcfs#zu*jvhvp52^df0u_4@-n^!n@e@b*@$3p4)kth'

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True

ALLOWED_HOSTS =  ['localhost', '127.0.0.1']

CORS_ALLOWED_ORIGINS = [
    "http://localhost:8080",
    "http://127.0.0.1:8080",
    "http://localhost:file",
    "http://192.168.1.40:8080",
]

CORS_ALLOW_CREDENTIALS = True

SESSION_COOKIE_SAMESITE = "None"

SESSION_COOKIE_SECURE = True

CORS_ORIGIN_WHITELIST = ("localhost:8080","127.0.0.1:8080")

CSRF_TRUSTED_ORIGINS = ["http://localhost:8080", "http://*.127.0.0.1", "http://127.0.0.1:8080","http://192.168.1.40:8080",]

INTERNAL_IPS = [
    "127.0.0.1",
]

AUTH_USER_MODEL = 'users.User'

AUTHENTICATION_BACKENDS = ["users.backends.EmailBackend"]

# Application definition

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    # THIRD PARTY APPS
    "rest_framework",
    "rest_framework.authtoken",
    'rest_framework_simplejwt',
    'djoser',
    "corsheaders",
    "django_filters",
    "django_extensions",
    'import_export',
    'stripe',
    # "captcha",
    
    # CUSTOM APPS
    'ads',
    'api',
    'companies',
    'connections',
    'locations',
    'mail',
    'notifications',
    'pages',
    'pitches',
    'posts',
    'profiles',
    'projects',
    'services',
    'users',
    'usersettings'
]

MIDDLEWARE = [
    "django.middleware.security.SecurityMiddleware",
    "django.contrib.sessions.middleware.SessionMiddleware",
    "django.middleware.locale.LocaleMiddleware",
    "corsheaders.middleware.CorsMiddleware",
    "django.middleware.common.CommonMiddleware",
    "django.middleware.csrf.CsrfViewMiddleware",
    "django.contrib.auth.middleware.AuthenticationMiddleware",
    "django.contrib.messages.middleware.MessageMiddleware",
    "django.middleware.clickjacking.XFrameOptionsMiddleware",
]

SESSION_ENGINE = 'django.contrib.sessions.backends.db'
SESSION_EXPIRE_AT_BROWSER_CLOSE = False  

ROOT_URLCONF = 'theapp.urls'

TEMPLATES = [
    {
        "BACKEND": "django.template.backends.django.DjangoTemplates",
        "DIRS": [BASE_DIR / "templates"],
        "APP_DIRS": True,
        "OPTIONS": {
            "context_processors": [
                "django.template.context_processors.debug",
                "django.template.context_processors.request",
                "django.contrib.auth.context_processors.auth",
                "django.contrib.messages.context_processors.messages",
            ],
        },
    },
]

WSGI_APPLICATION = 'theapp.wsgi.application'
ASGI_APPLICATION = "theapp.asgi.application"

# Database
# https://docs.djangoproject.com/en/3.2/ref/settings/#databases

DATABASES = {
    "default": {
        "ENGINE": "django.db.backends.postgresql",
        "NAME": "flook",
        "USER": "user542",
        "PASSWORD": "fsdfdfsd43",
        "PORT":"5433",
    }
}

DATETIME_INPUT_FORMATS = [
    "%m/%d/%Y %H:%M:%S.%f",  # '10/25/2006 14:30:59.000200'
    "%m/%d/%Y %H:%M",  # '10/25/2006 14:30'
    "%Y-%m-%d %H:%M:%S",  # '2006-10-25 14:30:59'
    "%Y-%m-%d %H:%M:%S.%f",  # '2006-10-25 14:30:59.000200'
    "%Y-%m-%d %H:%M",  # '2006-10-25 14:30'
    "%m/%d/%Y %H:%M:%S",  # '10/25/2006 14:30:59'
    "%m/%d/%y %H:%M:%S",  # '10/25/06 14:30:59'
    "%m/%d/%y %H:%M:%S.%f",  # '10/25/06 14:30:59.000200'
    "%m/%d/%y %H:%M",  # '10/25/06 14:30'
]
# Password validation
# https://docs.djangoproject.com/en/3.2/ref/settings/#auth-password-validators

PASSWORD_HASHERS = [
    'django.contrib.auth.hashers.PBKDF2PasswordHasher',
    'django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher',
    'django.contrib.auth.hashers.Argon2PasswordHasher',
    'django.contrib.auth.hashers.BCryptSHA256PasswordHasher',
    'django.contrib.auth.hashers.ScryptPasswordHasher',
]

AUTH_PASSWORD_VALIDATORS = [
    {
        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
    },
]

REST_FRAMEWORK = {
    "DEFAULT_AUTHENTICATION_CLASSES": [
        "rest_framework.authentication.SessionAuthentication",
        "rest_framework.authentication.TokenAuthentication",
        'rest_framework_simplejwt.authentication.JWTAuthentication',
    ],
    "DEFAULT_RENDERER_CLASSES": ("rest_framework.renderers.JSONRenderer",),
    "DEFAULT_PARSER_CLASSES": [
        "rest_framework.parsers.MultiPartParser",
        "rest_framework.parsers.JSONParser",
    ],
    "DEFAULT_FILTER_BACKENDS": ["django_filters.rest_framework.DjangoFilterBackend"],
}

SIMPLE_JWT = {
    'AUTH_HEADER_TYPES': ('JWT',),
    'ACCESS_TOKEN_LIFETIME': timedelta(days=1),
    'REFRESH_TOKEN_LIFETIME': timedelta(days=1),
}


# Internationalization
# https://docs.djangoproject.com/en/3.2/topics/i18n/

LANGUAGE_CODE = 'en-us'

TIME_ZONE = 'UTC'

USE_I18N = True

USE_L10N = True

USE_TZ = True


# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/3.2/howto/static-files/

STATIC_URL = "static/"

STATICFILES_DIRS = (os.path.join(BASE_DIR, "static/"),)

STATIC_ROOT = os.path.join(BASE_DIR, "staticfiles/")

MEDIA_ROOT = os.path.join(BASE_DIR, "storage").replace("\\", "/")

MEDIA_URL = "/storage/"

# Default primary key field type
# https://docs.djangoproject.com/en/4.1/ref/settings/#default-auto-field

DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"

CACHES = {
    "default": {
        "BACKEND": "django.core.cache.backends.memcached.PyMemcacheCache",
        "LOCATION": "127.0.0.1:11211",
    }
}

I don’t think I’m following what you’re saying here.

When you do something to establish a session, Django will return a session cookie in the response. That session cookie will be returned by the browser with every request.

So, when you log into the Django site, Django will return a cookie. All subsequent requests to that site will include the cookie, including AJAX requests.

Yes when I log into the site I get a cookie and that cookie is placed in the Browser’s storage and the cookie is called sessionid and the value is something like this “wxpn7fjbg3x4w61prb3xifqta1jz486n” correct?

Now if I make a request from my Vue app sitting on localhost:8080 to my Django app’s Rest Framework’s endpoints at say localhost:8000/api/v1/users/1 the request will include the cookie I mention here above because the browser attaches it to the request or what?

How is the cookie attached to subsequent requests?

And based on what you say here:
“All subsequent requests to that site will include the cookie, including AJAX requests.”
does it mean the request can come from anywhere, any domain any website, any application? Because then it means if I am logged in any person making a request can get my detail because the cookie is attached to the request?

That is why I asked if the cookie will only be attached to requests coming from hosts listed in the CORS settings. Or does the request have to come from the same browser?

Because at the moment I am logged in and I am making a request to some end point but access denied so the cookie is not getting attached to me request and this is what I am trying to find out why not

The requests must necessarilly come from the browser the cookie was acquired in. The CORS settings only allows a script (executed in the browser), whose Origin (server it was loaded from) is one of the allowed origins.

About SESSION_COOKIE_SAMESITE, for me with django 3.2, the correct value is None and not “None”, despite the documentation mention it is a string. This may be different with django 4.2. For me, using “None” in django 3.2 results in an error when django sends the cookie after login.

To be sure about settings values, can you give the server name / port for the backend application and the server name / port serving the vue application (this one must be in CORS_ALLOWED_ORIGINS)

I suspect some inconsistencies in your settings as you use SESSION_COOKIE_SECURE = True (which is necessary to allow resending of the cookie in cross origin requests), but other settings show the use of http instead of https.

Also, note that CORS_ORIGIN_WHITELIST can be removed from settings as it is replaced by CORS_ALLOWED_ORIGINS.

Also, please note that ajax request done from the vue application must set xmlHTTPRequest.withCredentials to true for the browser to automatically send the cookie with the request.

In your browser, before each ajax request from the vue application, you should see an OPTION request. Can you post the headers of the response of this OPTION request so that we can verify the correctness of CORS configuration

Thank you I presumed the request must come from same browser and I can see the cookie in the storage for the domain localhost:8000 which is the server running the Django back end. I am on my local PC and in development so have not uploaded it yet to any domain. Trying to sort out this same site login issue then I can. This is the only thing holding me back.

All requests to the DRF comes from localhost:8080 which is my Vue application.

So here are my settings now:

ALLOWED_HOSTS =  ['localhost', '127.0.0.1']

CORS_ALLOWED_ORIGINS = [
    "http://localhost:8080",
    "http://127.0.0.1:8080",
    "http://localhost:file",
    "http://192.168.1.40:8080",
]

CORS_ALLOW_CREDENTIALS = True

SESSION_COOKIE_SAMESITE = None

SESSION_COOKIE_SECURE = True


CSRF_TRUSTED_ORIGINS = ["http://localhost:8080", "http://*.127.0.0.1", "http://127.0.0.1:8080","http://192.168.1.40:8080",]

INTERNAL_IPS = [
    "127.0.0.1",
]

All is on my local PC so not using https

On my Vue side I have these settings:

import { createApp } from "vue";

import { createPinia } from "pinia";

import App from "./App.vue";

import router from "./router";

import axios from "axios";

import "./registerServiceWorker";

axios.defaults.baseURL = "http://localhost:8000";

axios.defaults.xsrfHeaderName = "X-CSRFToken";

axios.defaults.xsrfCookieName = "csrftoken";

axios.defaults.withCredentials = true;

const pinia = createPinia();

const app = createApp(App);

app.use(router);

app.use(pinia);

app.mount("#app");

I changed one endpoint to allow any and these are the Response Headers:

HTTP/1.1 200 OK
Date: Wed, 26 Jul 2023 02:11:35 GMT
Server: WSGIServer/0.2 CPython/3.10.6
Content-Type: application/json
Allow: GET, HEAD, OPTIONS
X-Frame-Options: DENY
Content-Length: 383
Vary: origin, Accept-Language, Cookie
access-control-allow-credentials: true
access-control-allow-origin: http://localhost:8080
Content-Language: en
X-Content-Type-Options: nosniff
Referrer-Policy: same-origin
Cross-Origin-Opener-Policy: same-origin

and the Request Headers:

GET /api/v1/services/categories/ HTTP/1.1
Host: localhost:8000
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0
Accept: application/json, text/plain, */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Origin: http://localhost:8080
Connection: keep-alive
Referer: http://localhost:8080/
Cookie: wp-settings-1=libraryContent%3Dbrowse%26amp%3Dundefined%26editor%3Dhtml%26imgsize%3Dfull%26siteorigin_panels_setting_tab%3Dwelcome; wp-settings-time-1=1690310901
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-site
Pragma: no-cache
Cache-Control: no-cache

There is no OPTION preflight request and really not sure why.

What’s strange though is even though I am logged in if I just go to http://localhost:8000/api/v1/users/1/ then I get “authentication credentials were not provided” but I am logged in why can I not access that endpoint from the same browser and tab with the cookie clearly visible in the browser? Even logged in on Chrome in admin I cannot access any endpoints which I was previously able to do.

This makes no sense because on Firefox if I change to http://127.0.0.1:8000/api/v1/users/1/ I can access it fine and on Chrome I have to change the URL to http://localhost:8000/api/v1/users/1/ then it works.

But no matter what I do in the Vue app I cannot access any endpoint because my access is denied for not providing credentials.