Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Login/Logout API endpoints not sending/clearing django sessionid cookie #442

Closed
edmundsj opened this issue Oct 21, 2022 · 9 comments
Closed

Comments

@edmundsj
Copy link

edmundsj commented Oct 21, 2022

EDITED
TLDR: Part of the problem was that I was not including credentials on my frontend, and this got the login method working. However, I am still unable to get the logout function to work

I have followed the installation instructions, and am trying to use dj-rest-auth with a react SPA. It appears to be partly working, as when I log in via the django admin console, and I make a GET request to
/api/account/user/ (my user endpoint for dj-rest-auth), I get back the user info which corresponds to my user. This makes me think nothing is wrong with at least the basic configuration of dj-rest-auth. When I log out from the admin console and hit the same endpoint, I get a 403 response with a JSON response that tells me no user is logged in. Great.

However, the login/logout endpoints don't appear to be working. When I log in via the django admin console, and then I hit the logout endpoint with my SPA, it does not log me out. The django server tells me the request was good:

[21/Oct/2022 17:32:18] "POST /api/account/logout/ HTTP/1.0" 200 37

But I am still logged in to the console, and no change was made to the Session model database.

Similarly, when I hit the login endpoint with a POST request, using exactly the same username/password as I use in the admin console, it does not log me in. However, the login request gives me back a 200 response:

[21/Oct/2022 17:39:58] "POST /api/account/login/ HTTP/1.0" 200 50

which seems odd. dj-rest-auth seems to think that it's working, but for some reason it doesn't appear to be attaching / detaching users from sessions. Any ideas as to why this might be, or how to debug it further?

@edmundsj
Copy link
Author

I have verified that in both the login/logout cases, no changes are made to the Session model database.

@edmundsj
Copy link
Author

I also checked the create account functionality, and I am able to create user accounts. Hitting the registration endpoint with an email / password1 / password2 creates a user in the database and I can view this user from the django admin console.

@edmundsj
Copy link
Author

After further setting of breakpoints and debugging, it looks likedj-rest-auth IS in fact making it all the way to calling its internal process_login() method, and calls django.contrib.auth.login() with a valid username and request, but for some reason django itself is not adding attaching the user to a session. This is utterly bizarre. Any thoughts or further suggestions? Bueller?

@edmundsj
Copy link
Author

Perhaps using the wrong authentication backend? Here is my settings.py

"""
Django settings for axle_backend project.

Generated by 'django-admin startproject' using Django 4.1.

For more information on this file, see
https://docs.djangoproject.com/en/4.1/topics/settings/

For the full list of settings and their values, see
https://docs.djangoproject.com/en/4.1/ref/settings/
"""

from pathlib import Path
from decouple import config
import os

# 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/4.1/howto/deployment/checklist/

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = config('DJANGO_SECRET_KEY')

# This will be true only if we are not in production
DEBUG = config('DEBUG', cast=bool)
DEVELOPMENT = DEBUG
PRODUCTION = not DEVELOPMENT

LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'handlers': {
        'console': {
            'class': 'logging.StreamHandler',
        },
    },
    'root': {
        'handlers': ['console'],
        'level': 'DEBUG',
    },
    #'loggers': {
        #'django.request': {
            #'handlers': ['console'],
            #'level': 'DEBUG',
            #'propagate': False,
        #}
    #}
}

ALLOWED_HOSTS = ['.localhost', '.askaxle.com', '127.0.0.1', '127.0.0.1:3000']
if not PRODUCTION:
    ALLOWED_HOSTS += ['testserver']


# Application definition

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'rest_framework',
    'rest_framework.authtoken',
    'corsheaders',
    'learn',
    'channels',
    'drf_spectacular',
    'sass_processor',
    'users',
    'estimate',
    'forum',
    'django.contrib.sites',
    'allauth',
    'allauth.account',
    'allauth.socialaccount',
    'dj_rest_auth',
    'dj_rest_auth.registration',

    'phonenumber_field',

]

REST_FRAMEWORK = {
    'DEFAULT_SCHEMA_CLASS': 'drf_spectacular.openapi.AutoSchema',
    'DEFAULT_RENDERER_CLASSES': [
        'rest_framework.renderers.JSONRenderer',
    ],
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework.authentication.SessionAuthentication',
    ],
}
SPECTACULAR_SETTINGS = {
    'TITLE': 'Axle backend API',
    'DESCRIPTION': 'API to facilitate communication with all frontends',
    'VERSION': '0.0.1',
    'SERVE_INCLUDE_SCHEMA': False,
}

MIDDLEWARE = [
    'corsheaders.middleware.CorsMiddleware',
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
    #'axle_backend.logging.LogMiddleware',
]
#if not PRODUCTION:
    #MIDDLEWARE += 'request_logging.middleware.LoggingMiddleware'

ROOT_URLCONF = 'axle_backend.urls'

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [],
        '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 = 'axle_backend.wsgi.application'
ASGI_APPLICATION = 'axle_backend.asgi.application'


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

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql_psycopg2',
        'NAME': config('DATABASE_NAME'),
        'USER': config('DATABASE_USERNAME'),
        'PASSWORD': config('DATABASE_PASSWORD'),
        'HOST': config('DATABASE_HOST'),
        'PORT': config('DATABASE_PORT'),
    }
}

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

LANGUAGE_CODE = 'en-us'

#TIME_ZONE = 'UTC'

USE_I18N = True

USE_TZ = True


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

STATIC_URL = 'static/django/'
STATIC_ROOT = BASE_DIR / Path('static')

FIXTURE_DIRS = [BASE_DIR / Path('fixtures')]

STATICFILES_FINDERS = [
    'django.contrib.staticfiles.finders.FileSystemFinder',
    'django.contrib.staticfiles.finders.AppDirectoriesFinder',
    'sass_processor.finders.CssFinder',
]


DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'

###############################################################
# AUTHENTICATION #
###############################################################


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',
    },
]

AUTH_USER_MODEL = 'users.User'
USER_MODEL_USERNAME_FIELD = 'email'
ACCOUNT_USERNAME_REQUIRED = False
ACCOUNT_AUTHENTICATION_METHOD = ('email')
ACCOUNT_EMAIL_REQUIRED = True
ACCOUNT_UNIQUE_EMAIL = True
ACCOUNT_USER_MODEL_USERNAME_FIELD = None # This has to be added so that allauth knows not to try to use 'username'

EMAIL_HOST = config('EMAIL_HOST')
EMAIL_HOST_USER = config('EMAIL_HOST_USERNAME')
EMAIL_HOST_PASSWORD = config('EMAIL_HOST_PASSWORD')
EMAIL_PORT = 587
EMAIL_USE_TLS = True
DEFAULT_FROM_EMAIL = config('DEFAULT_FROM_EMAIL')

CORS_ALLOWED_ORIGINS = [
    'http://localhost:3000', 'http://localhost:80', 'http://askaxle.com', 'https://askaxle.com',
]
CSRF_TRUSTED_ORIGINS = CORS_ALLOWED_ORIGINS

if PRODUCTION:
    CORS_ORIGIN_ALLOW_ALL = False
else:
    CORS_ORIGIN_ALLOW_ALL = True

CORS_ALLOW_CREDENTIALS = True
CORS_EXPOSE_HEADERS = ['Content-Type', 'X-CSRFToken']

CSRF_COOKIE_SAMESITE = 'Lax'
SESSION_COOKIE_SAMESITE = 'Lax'
CSRF_COOKIE_HTTPONLY = True
SESSION_COOKIE_HTTPONLY = True

if PRODUCTION:
    CSRF_COOKIE_SECURE = True
    SESSION_COOKIE_SECURE = True

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

SITE_ID = 1

@edmundsj
Copy link
Author

edmundsj commented Oct 22, 2022

After deleting all existing user sessions from the django_session database using a manual SQL query, it looks like logging in DOES actually create a new session in the database, but it does not allow me to view user information via the user endpoint, and (for the same reason I suspect) logging out does nothing. This smells to me like the Set-Cookie header is not being transmitted with the sessionid.

@edmundsj
Copy link
Author

edmundsj commented Oct 22, 2022

OK, I've identified the problem - as I suspected, the sessionid cookie is never getting set on the client side. Despite a successful login, a 204 response from dj-rest-auth, and a valid session with an expiry date in the future being added to the django_session database, no cookie is being transmitted to the client. What within dj-rest-auth or django is responsible for this?

The cookie does get set when logging in via the admin console, (verified with chrome dev tools), but not the dj-rest-auth login endpoint, which explains why I can hit the user endpoint after logging in via the admin console. What within django or dj-rest-auth is responsible for setting this cookie? How might I go about fixing this problem?

However, when performing a logout, the database is completely unaffected after logging in via the admin console. I suspect this also has to do with the cookie not getting properly transmitted.

@edmundsj edmundsj changed the title Login/Logout API endpoints not interacting with django session Login/Logout API endpoints not sending/clearing django sessionid cookie Oct 22, 2022
@edmundsj
Copy link
Author

edmundsj commented Oct 22, 2022

The login problem was one on my frontend - I was not including credentials with the API client I was using. After including them, the login endpoint now works.

However, I'm still getting a 403 error when I hit the logout endpoint, and I am not sure why. It's especially weird because there is no 403 error defined in the LogoutView of dj-rest-auth. Why might I be getting a 403 error, and who is the one generating it? Might it be a piece of middleware?

[22/Oct/2022 09:30:44] "POST /api/account/logout/ HTTP/1.0" 403 45

@edmundsj
Copy link
Author

Sure enough, the post method of the LogoutView is never even being called. I don't see a LogoutSerializer, so where would I start to debug this?

@edmundsj
Copy link
Author

I've tracked this down to a CSRF token missing error by creating a piece of middleware that logs the content of django responses in the runserver console. Not a dj-rest-auth problem. Closing the issue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant