Search code examples
pythonmysqldjangodjango-authentication

Django Login will Authenticate but redirect fails only on remote server


I have just finished building my web app and migrated it to a digital ocean droplet. The problem that I am having is I can run runserver 0.0.0.0:8000 and use my server IP to get to my homepage, landing-page, signup etc. As well I am able to enter incorrect credentials and be redirected back to sign-in again. As soon as the correct credentials are entered Django hangs up until I stop the server.

On my local machine I do not have this problem, as well by connecting through LAN I am able to login successfully. My local machine connects to a database on a droplet in the same datacenter as my Django prod server.

Strangely, According to my database, the attempt to login is successful. The issue seems to be something related to redirecting to my view. by changing the redirect to my landing page and adding print statements to my view I can see that it is never accessed following authentication.

Mysql version is identical across both instances.

From my Django Instance:

  • I can login to the database from my instance
  • I can create a new SuperUser from my instance manage.py shell
  • I can fill out the signup form and create a new user (but my shell hangs until CTR + C)
  • I can login & prove that I am authenticated from manage.py shell
  • I can confirm my server is not removing my headers
  • I can query my users from manage.py shell
  • I am not receiving any errors
  • my server is not utilizing too much CPU
  • allowed_hosts=['*']
  • I have commented out all security features
  • NGINX & Gunicorn are disabled (the same issue exists with them enabled)

As well I have cloned my repo on the same instance as my database. unfortunately, this did not resolve my issue.

I am using : python3.6.8 - Django 2.2.1 - MySQL 5.7

settings.py

import os
 
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))


SECRET_KEY = os.environ['SECRET_KEY']

DEBUG = True

ALLOWED_HOSTS = ['*']


# Application definition

INSTALLED_APPS = [
    #django builtin Imports
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'django.contrib.sites',

    #django installed pacakges imports
    'allauth',
    'allauth.account',
    'crispy_forms',
    'jquery',
    'rest_framework',
    'rest_framework_datatables',
    'chartjs',
    'sass_processor',
    'django_nose',

    #user built imports
    'users',
    'pages',
    'authenticated',
    'exchange',
    'celerytasks'

]

    REST_FRAMEWORK = {
    'DEFAULT_RENDERER_CLASSES': (
        'rest_framework.renderers.JSONRenderer',
        'rest_framework.renderers.BrowsableAPIRenderer',
        'rest_framework_datatables.renderers.DatatablesRenderer',
    ),
    'DEFAULT_FILTER_BACKENDS': (
        'rest_framework_datatables.filters.DatatablesFilterBackend',
    ),
    'DEFAULT_PAGINATION_CLASS': (
                  'rest_framework_datatables.pagination.DatatablesPageNumberPagination'
        ),
    'PAGE_SIZE': 3,
}


MIDDLEWARE = [
    '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',
    'django.contrib.auth.middleware.RemoteUserMiddleware',
]

TEST_RUNNER = 'django_nose.NoseTestSuiteRunner'


ROOT_URLCONF = 'rubillion.urls'

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': ['templates'],
        'APP_DIRS': True,
        'OPTIONS': {
            'libraries':{
                'file_exists': 'authenticated.templatetags.app_filters'
            },
            '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 = 'rubillion.wsgi.application'

#SESSION_ENGINE = "django.contrib.sessions.backends.cache" 

DATABASES = {
    'default': {
        'HOST': os.environ['HOST'],
        'NAME': 'user_db',
        'ENGINE': 'django.db.backends.mysql',
        'USER': os.environ['DBUSER'],
        'PASSWORD': os.environ['DBPWD']
    },
    'auth_db': {
        'HOST': os.environ['HOST'],
        'NAME': 'user_db',
        'ENGINE': 'django.db.backends.mysql',
        'USER': os.environ['DBUSER'],
        'PASSWORD': os.environ['DBPWD']
    },
    'exchange_db': {
        'HOST': os.environ['HOST'],
        'NAME': 'exchange',
        'ENGINE': 'django.db.backends.mysql',
        'USER': os.environ['DBUSER'],
        'PASSWORD': os.environ['DBPWD']
    },
}

DATABASE_ROUTERS = ['rubillion.routers.ExchangeRouter',     'rubillion.routers.AuthRouter']
# Password validation
# https://docs.djangoproject.com/en/2.1/ref/settings/#auth-password-    validators

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


LANGUAGE_CODE = 'en-us'

TIME_ZONE = 'UTC'

USE_I18N = True

USE_L10N = True

USE_TZ = True


STATIC_URL = '/static/'
STATICFILES_DIRS = [
    os.path.join(BASE_DIR, "static")
]

AUTH_USER_MODEL = 'users.CustomUser'

EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'

LOGIN_REDIRECT_URL = 'pages:home'
ACCOUNT_LOGOUT_REDIRECT_URL = 'pages:home'

AUTHENTICATION_BACKENDS = (
    "django.contrib.auth.backends.RemoteUserBackend",
    "allauth.account.auth_backends.AuthenticationBackend",
    "django.contrib.auth.backends.ModelBackend",
)

SITE_ID = 1

ACCOUNT_EMAIL_REQUIRED = True
ACCOUNT_USERNAME_REQUIRED = False
ACCOUNT_SIGNUP_FORM_CLASS = "users.forms.CustomUserCreationForm"
ACCOUNT_SIGNUP_PASSWORD_ENTER_TWICE = False
ACCOUNT_SESSION_REMEMBER = True
ACCOUNT_AUTHENTICATION_METHOD = 'email'
ACCOUNT_UNIQUE_EMAIL = True

CRISPY_TEMPLATdE_PACK = 'bootstrap4'


MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
MEDIA_URL = '/media/'


'''
    --------------------------------------------------
    
    ALL SECURITY CONCERNS MUST BE ADDRESSED BELOW THIS LINE

    --------------------------------------------------
'''
#SECURE_BROWSER_XSS_FILTER = True
#SECURE_CONTENT_TYPE_NOSNIFF = True
#SECURE_HSTS_INCLUDE_SUBDOMAINS = True
#SECURE_SSL_REDIRECT = True
#SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
#SESSION_COOKIE_SECURE = False
#CSRF_COOKIE_SECURE = True
#SECURE_HSTS_SECONDS = 60

#PREPEND_WWW = True
#BASE_URL = "https://###"

#X_FRAME_OPTIONS = 'DENY'
#SECURE_HSTS_PRELOAD = True

wsgi.py

import os

from django.core.wsgi import get_wsgi_application

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'rubillion.settings')

application = get_wsgi_application()

relevant urls.py

from django.urls import path, include, re_path
from .views import *
from rest_framework import routers, renderers


app_name = "pages"
router = routers.DefaultRouter())

urlpatterns = [
    path('', HomePageView.as_view(), name='home'),
]

How I created superuser & check if they exist

(rubyenv)$ python3 manage.py shell
>>> from users.models import CustomUser;
>>> CustomUser.objects.create_superuser('admin', 'a@gmail.com',       '$Admin11')


(rubyenv)$ python3 manage.py shell
>>> from users.models import CustomUser
>>> c = CustomUser.objects.filter(is_superuser=True)
>>> print(c)
<QuerySet [
    <CustomUser: **@gmail.com>, 
    <CustomUser: admin@example.com>, 
    <CustomUser: **@test.com>
    ]>

My Homepage view that is being redirected. I added two print statements to verify headers match the headers on my local machine.

class HomePageView(TemplateView):
    template_name = 'pages/home.html'

    def get(self, request):
        #print(request.headers)
        #print(request.META)
        return render(request, self.template_name, {})

My error log when I have to cancel create_superuser (User is created regardless)

 File "/home/antony/ruby/rubyenv/lib/python3.6/site-packages/django/contrib/auth/models.py", line 162, in create_superuser
    return self._create_user(username, email, password, **extra_fields)
  File "/home/antony/ruby/rubyenv/lib/python3.6/site-packages/django/contrib/auth/models.py", line 145, in _create_user
    user.save(using=self._db)
  File "/home/antony/ruby/rubyenv/lib/python3.6/site-packages/django/contrib/auth/base_user.py", line 66, in save
    super().save(*args, **kwargs)
  File "/home/antony/ruby/rubyenv/lib/python3.6/site-packages/django/db/models/base.py", line 741, in save
    force_update=force_update, update_fields=update_fields)
  File "/home/antony/ruby/rubyenv/lib/python3.6/site-packages/django/db/models/base.py", line 790, in save_base
    update_fields=update_fields, raw=raw, using=using,
  File "/home/antony/ruby/rubyenv/lib/python3.6/site-packages/django/dispatch/dispatcher.py", line 175, in send
    for receiver in self._live_receivers(sender)
  File "/home/antony/ruby/rubyenv/lib/python3.6/site-packages/django/dispatch/dispatcher.py", line 175, in <listcomp>
    for receiver in self._live_receivers(sender)
  File "/home/antony/ruby/authenticated/models.py", line 320, in create_user_profile
    Users.objects.get_or_create(username=instance)
  File "/home/antony/ruby/rubyenv/lib/python3.6/site-packages/django/db/models/manager.py", line 82, in manager_method
    return getattr(self.get_queryset(), name)(*args, **kwargs)
  File "/home/antony/ruby/rubyenv/lib/python3.6/site-packages/django/db/models/query.py", line 541, in get_or_create
    return self._create_object_from_params(kwargs, params)
  File "/home/antony/ruby/rubyenv/lib/python3.6/site-packages/django/db/models/query.py", line 575, in _create_object_from_params
    obj = self.create(**params)
  File "/home/antony/ruby/rubyenv/lib/python3.6/site-packages/django/db/models/query.py", line 422, in create
    obj.save(force_insert=True, using=self.db)
  File "/home/antony/ruby/rubyenv/lib/python3.6/site-packages/django/db/models/base.py", line 741, in save
    force_update=force_update, update_fields=update_fields)
  File "/home/antony/ruby/rubyenv/lib/python3.6/site-packages/django/db/models/base.py", line 790, in save_base
    update_fields=update_fields, raw=raw, using=using,
  File "/home/antony/ruby/rubyenv/lib/python3.6/site-packages/django/dispatch/dispatcher.py", line 175, in send
    for receiver in self._live_receivers(sender)
  File "/home/antony/ruby/rubyenv/lib/python3.6/site-packages/django/dispatch/dispatcher.py", line 175, in <listcomp>
    for receiver in self._live_receivers(sender)
  File "/home/antony/ruby/exchange/models.py", line 49, in queue_task
    send_task('celerytasks.tasks.volatilityTrader')
  File "/home/antony/ruby/rubyenv/lib/python3.6/site-packages/celery/local.py", line 191, in __call__
    return self._get_current_object()(*a, **kw)
  File "/home/antony/ruby/rubyenv/lib/python3.6/site-packages/celery/app/base.py", line 756, in send_task
    amqp.send_task_message(P, name, message, **options)
  File "/home/antony/ruby/rubyenv/lib/python3.6/site-packages/celery/app/amqp.py", line 552, in send_task_message
    **properties
  File "/home/antony/ruby/rubyenv/lib/python3.6/site-packages/kombu/messaging.py", line 181, in publish
    exchange_name, declare,
  File "/home/antony/ruby/rubyenv/lib/python3.6/site-packages/kombu/connection.py", line 510, in _ensured
    return fun(*args, **kwargs)
  File "/home/antony/ruby/rubyenv/lib/python3.6/site-packages/kombu/messaging.py", line 187, in _publish
    channel = self.channel
  File "/home/antony/ruby/rubyenv/lib/python3.6/site-packages/kombu/messaging.py", line 209, in _get_channel
    channel = self._channel = channel()
  File "/home/antony/ruby/rubyenv/lib/python3.6/site-packages/kombu/utils/functional.py", line 44, in __call__
    value = self.__value__ = self.__contract__()
  File "/home/antony/ruby/rubyenv/lib/python3.6/site-packages/kombu/messaging.py", line 224, in <lambda>
    channel = ChannelPromise(lambda: connection.default_channel)
  File "/home/antony/ruby/rubyenv/lib/python3.6/site-packages/kombu/connection.py", line 852, in default_channel
    self.ensure_connection(**conn_opts)
  File "/home/antony/ruby/rubyenv/lib/python3.6/site-packages/kombu/connection.py", line 422, in ensure_connection
    callback, timeout=timeout)
  File "/home/antony/ruby/rubyenv/lib/python3.6/site-packages/kombu/utils/functional.py", line 355, in retry_over_time
    sleep(1.0)

I have gone through every stack overflow question I could find and tried what feels like every solution. I have been debugging this problem for 4 days and am in desperate need of some help. If there is any information I forgot to provide please let me know :)

SOLVED:
As pointed out by Sidnei Pereira answer the problem is celery related.

I did not setup rabbitmq on my remote server. I was waiting for to complete setting up Gunicorn and Nginx first and I did not need it for authentication. In another app exchange/models.py

I used a receiver decorator and omitted sender=model_name. I planned to run a celery task when new price records are added to db. Instead this event would fire anytime any record is added to db

class Price(models.Model):
    ModelField
    ModelField
    objects = models.Manager()

    def __str__(self):
        return self.pair + ' / ' + str(self.timestamp)

    @receiver(post_save)
    def queue_task(instance, created, **kwargs):
        send_task('celerytasks.tasks.volatilityTrader')
        send_task('celerytasks.tasks.swingTrader')

    class Meta:
        managed = False
 

Solution:

  class Price(models.Model):
    ModelField
    ModelField
    objects = models.Manager()

    def __str__(self):
        return self.pair + ' / ' + str(self.timestamp)

    class Meta:
        managed = False
 
#reciever must go after model is defined and a sender arg is required

@receiver(post_save, sender=Price)
def queue_task(instance, created, **kwargs):
    send_task('celerytasks.tasks.volatilityTrader')
    send_task('celerytasks.tasks.swingTrader')
        


Solution

  • By the traceback I figure you are using celery for an asynchronous task by the login moment. This celery task seems to be triggered by a Django signal when saving the User model. Since it's hang for a long time I think it's because when you celery to push a message to the broker, it can't reach it. That could means Celery isn't properly configured (or not configured at all) in this remote environment or the Message Broker it connects to (RabbitMQ, Redis, SQS, Kafka, etc.) is not running.

    Well, if you have no idea about the use of celery (I guess you do since it's in INSTALLED_APPS) in the project - like "it works fine without any broker running in local environment", which means some debug flag is ignoring this - I guess another package you're using depends and uses Celery.

    Some things to debug:

    File "/home/antony/ruby/authenticated/models.py", line 320, in create_user_profile
    Users.objects.get_or_create(username=instance)
    

    Looks like this is being executed because of a Django signal

    File "/home/antony/ruby/exchange/models.py", line 49, in queue_task
        send_task('celerytasks.tasks.volatilityTrader')
    

    See? Here a message/task is being sent to the broker's queue so an network connection is involved (unless you are using CELERY_TASK_ALWAYS_EAGER), probably that's why the server never responds.