'(UPDATED)(Django-React App) API request to the backend returns 200 OK but not getting authenticated in frontend

What am I developing?:

Hello, I am developing a Django-React App, where Django acts as backend for the authentication using Djoser library and JWT, and React acts as frontend application. In addition, I use Django-rest-framework, CORS headers, Axios and React Redux to do all the communication between backend and frontend.

Context of the problem:

I work on an Ubuntu 20.04, called 'pablo-dev.ivanlab.lan', through SSH connection on Visual Code, and I launch there the docker-compose file which start the microservices in docker containers that are exposed to the Ubuntu machine, so all the testing I have been doing is through the SSH on Visual Code.

I am building a microservices architecture as a Django-React-Postgres application in docker-compose so I can deploy it on a Raspberyy Pi 4 with Raspbian OS. As the Raspberry is gonna run the frontend application in different IPs I decided to allow all origins in the CORS variables of Djando 'settings.py' instead of the static IP I use right now in SSH development.

The problem:

Once I try to change the 'CORS_ALLOW_ALL_ORIGINS' to 'True', my system keeps receiving the requests to the backend but even with a 200 HTTP response I am not getting authenticated in my frontend.


(UPDATE)

I have checked the console logs on the browser an it seems like I found the error, it has something to do with CORS Headers, I will attach the console log error.

Access to XMLHttpRequest at 'http://pablo-dev.ivanlab.lan:8000/auth/jwt/create/' from origin 'http://pablo-dev.ivanlab.lan:3000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

POST http://pablo-dev.ivanlab.lan:8000/auth/jwt/create/ net::ERR_FAILED 200

I have tried this mainly on the Login functionality where, first I do a POST request to login which returns a 200 HTTP response, but the second request which is load user is never done.

System images and states triggered in frontend application (wrong case / NOT expected behabiour)

(EXPECTED BEHAVIOUR)

Allowing the static IP in the 'CORS_ALLOWED_ORIGINS = [pablo-dev.ivanlab.lan:3000]' variable, both requests work as they should and I get authenticated.

Log of backend API requests (successful case / expected behabiour):

backend_1 | [18/May/2022 01:05:20] "POST /auth/jwt/create/ HTTP/1.1" 200 438

backend_1 | [18/May/2022 01:05:20] "GET /auth/users/me/ HTTP/1.1" 200 70

System images and states triggered in frontend application (successful case / expected behabiour)

I will now attach the main files that are inlcuded in this workflow:

settings.py (It's python script but dont try to run it cause it wont do anything)

from pathlib import Path
from datetime import timedelta
import os
import django.core

# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))


# 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 = '*****************************************'

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

ALLOWED_HOSTS = ["*"]
CORS_ALLOW_ALL_ORIGINS: True

# Hosts permitidos para hacer peticiones al backend
#CORS_ALLOWED_ORIGINS = [ 
#    "http://pablo-dev.ivanlab.lan:3000"
#]


# Application definition

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'corsheaders',
    'rest_framework',
    'rest_framework_simplejwt.token_blacklist',
    'djoser',
    'user_accounts'
]

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    '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',
]

ROOT_URLCONF = 'tfg_app.urls'

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [os.path.join(BASE_DIR, 'build')],
        '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 = 'tfg_app.wsgi.application'


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

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': '**************',
        'USER': '********',
        'PASSWORD': '************',
        'HOST': 'postgres'
    }
}


EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_HOST = 'smtp.gmail.com'
EMAIL_PORT = 587
EMAIL_HOST_USER = '***********'
EMAIL_HOST_PASSWORD = '***********'
EMAIL_USE_TLS = True


# Password validation
# https://docs.djangoproject.com/en/3.2/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',
    },
]


# 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/'
STAITCFILES_DIRS = [
    os.path.join(BASE_DIR, 'build/static')
]
STATIC_ROOT = os.path.join(BASE_DIR, 'static')


# Email Domain and Site Name configuration

DOMAIN = ('pablo-dev.ivanlab.lan:3000') 
SITE_NAME = ('Nelium Analytics') 


# Djoser config
# https://djoser.readthedocs.io/en/latest/settings.html

DJOSER = {
    'LOGIN_FIELD': 'email',
    'USER_CREATE_PASSWORD_RETYPE': True,
    'USERNAME_CHANGED_EMAIL_CONFIRMATION': True,
    'PASSWORD_CHANGED_EMAIL_CONFIRMATION': True,
    'SEND_CONFIRMATION_EMAIL': True,
    'SEND_ACTIVATION_EMAIL': True,
    'SET_PASSWORD_RETYPE': True,
    'PASSWORD_RESET_CONFIRM_RETYPE': True,
    'PASSWORD_RESET_CONFIRM_URL': 'password/reset/confirm/{uid}/{token}',
    'USERNAME_RESET_CONFIRM_URL': 'email/reset/confirm/{uid}/{token}',
    'ACTIVATION_URL': 'activate/{uid}/{token}',
    'SERIALIZERS': {
        'user_create': 'user_accounts.serializers.UserCreateSerializer',
        'user': 'user_accounts.serializers.UserCreateSerializer',
        'user_delete': 'djoser.serializers.UserDeleteSerializer',
    }

}

    # Json web token authentication
    # https://djoser.readthedocs.io/en/latest/authentication_backends.html

REST_FRAMEWORK = {
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.IsAuthenticated'
    ],
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework_simplejwt.authentication.JWTAuthentication',
    ),
}

SIMPLE_JWT = {
    'AUTH_HEADER_TYPES': ('JWT',),
    'ACCESS_TOKEN_LIFETIME': timedelta(minutes=60),
    'REFRESH_TOKEN_LIFETIME': timedelta(days=1),
    'ROTATE_REFRESH_TOKENS': False,
    'BLACKLIST_AFTER_ROTATION': False,
}


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

DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'


# Default User model used

AUTH_USER_MODEL = 'user_accounts.UserAccount'

auth.js (/actions)

import axios from 'axios';
import { setAlert } from './alert';
import {
    LOGIN_SUCCESS,
    LOGIN_FAIL,
    SIGNUP_SUCCESS,
    SIGNUP_FAIL,
    ACTIVATION_SUCCESS,
    ACTIVATION_FAIL,
    USER_LOADED_SUCCESS,
    USER_LOADED_FAIL,
    AUTHENTICATED_SUCCESS,
    AUTHENTICATED_FAIL,
    PASSWORD_RESET_SUCCESS,
    PASSWORD_RESET_FAIL,
    PASSWORD_RESET_CONFIRM_SUCCESS,
    PASSWORD_RESET_CONFIRM_FAIL,
    LOGOUT
} from './types';

export const checkAuthenticated = () => async dispatch => {
    if (localStorage.getItem('access')) {
        const config = {
            headers: {
                'Content-Type': 'application/json',
                'Accept': 'application/json'
            }
        }; 

        const body = JSON.stringify({ token: localStorage.getItem('access') });

        try {
            const res = await axios.post(`http://pablo-dev.ivanlab.lan:8000/auth/jwt/verify/`, body, config)

            if (res.status === 200) {
                dispatch({
                    type: AUTHENTICATED_SUCCESS
                });
            } else {
                dispatch({
                    type: AUTHENTICATED_FAIL
                });
            }
        } catch (err) {
            dispatch({
                type: AUTHENTICATED_FAIL
            });
        }
    } else {
        dispatch({
            type: AUTHENTICATED_FAIL
        });
    }
};

export const load_user = () => async dispatch => {
    if (localStorage.getItem('access')) {
        const config = {
            headers: {
                'Content-Type': 'application/json',
                'Authorization': `JWT ${localStorage.getItem('access')}`,
                'Accept': 'application/json'
            }
        }; 

        try {
            const res = await axios.get(`http://pablo-dev.ivanlab.lan:8000/auth/users/me/`, config);
    
            dispatch({
                type: USER_LOADED_SUCCESS,
                payload: res.data
            });
        } catch (err) {
            dispatch({
                type: USER_LOADED_FAIL
            });
        }
    } else {
        dispatch({
            type: USER_LOADED_FAIL
        });
    }
};

export const login = (email, password) => async dispatch => {
    const config = {
        headers: {
            'Content-Type': 'application/json'
        }
    };

    const body = JSON.stringify({ email, password });

    try {
        const res = await axios.post(`http://pablo-dev.ivanlab.lan:8000/auth/jwt/create/`, body, config);

        dispatch({
            type: LOGIN_SUCCESS,
            payload: res.data
        });

        dispatch(load_user());

        dispatch(setAlert('Inicio de sesión exitoso', 'success'));
    } catch (err) {
        dispatch({
            type: LOGIN_FAIL
        });

        dispatch(setAlert('Email/Contraseña erróneos', 'error'));
    }
};

export const signup = (name, email, password, re_password) => async dispatch => {
    if (password === re_password) {
        const config = {
            headers: {
                'Content-Type': 'application/json'
            }
        };

        const body = JSON.stringify({ name, email, password, re_password });

        try {
            const res = await axios.post(`http://pablo-dev.ivanlab.lan:8000/auth/users/`, body, config);

            dispatch({
                type: SIGNUP_SUCCESS,
                payload: res.data
            });

            dispatch(setAlert('Registro de cuenta exitoso', 'success'));
        } catch (err) {
            dispatch({
                type: SIGNUP_FAIL
            })
            dispatch(setAlert('Registro de cuenta erróneo', 'error'));
        }
    } else {
        dispatch({
            type: SIGNUP_FAIL
        });
    }
};

export const verify = (uid, token) => async dispatch => {
    const config = {
        headers: {
            'Content-Type': 'application/json'
        }
    };

    const body = JSON.stringify({ uid, token });

    try {
        await axios.post(`http://pablo-dev.ivanlab.lan:8000/auth/users/activation/`, body, config);

        dispatch({
            type: ACTIVATION_SUCCESS,
        });

        dispatch(setAlert('Activación de cuenta exitosa', 'success'));
    } catch (err) {
        dispatch({
            type: ACTIVATION_FAIL
        });

        dispatch(setAlert('Activación de cuenta errónea', 'error'));
    }
};

export const reset_password = (email) => async dispatch => {
    const config = {
        headers: {
            'Content-Type': 'application/json'
        }
    };

    const body = JSON.stringify({ email });

    try {
        await axios.post(`http://pablo-dev.ivanlab.lan:8000/auth/users/reset_password/`, body, config);

        dispatch({
            type: PASSWORD_RESET_SUCCESS
        });
        dispatch(setAlert('Solicitud de cambio de contraseña exitosa', 'success'));
    } catch (err) {
        dispatch({
            type: PASSWORD_RESET_FAIL
        });
        dispatch(setAlert('Solicitud de cambio de contraseña erróneo', 'error'));
    }
};

export const reset_password_confirm = (uid, token, new_password, re_new_password) => async dispatch => {
    const config = {
        headers: {
            'Content-Type': 'application/json'
        }
    };

    const body = JSON.stringify({ uid, token, new_password, re_new_password });

    try {
        await axios.post(`http://pablo-dev.ivanlab.lan:8000/auth/users/reset_password_confirm/`, body, config);

        dispatch({
            type: PASSWORD_RESET_CONFIRM_SUCCESS
        });

        dispatch(setAlert('Cambio de contraseña exitoso', 'success'));
    } catch (err) {
        dispatch({
            type: PASSWORD_RESET_CONFIRM_FAIL
        });
    }
};

export const logout = () => dispatch => {
    dispatch({
        type: LOGOUT
    });

    dispatch(setAlert('Cierre de sesión exitoso', 'success'));
};

auth.js (/reducers)

import {
    LOGIN_SUCCESS,
    LOGIN_FAIL,
    SIGNUP_SUCCESS,
    SIGNUP_FAIL,
    ACTIVATION_SUCCESS,
    ACTIVATION_FAIL,
    USER_LOADED_SUCCESS,
    USER_LOADED_FAIL,
    AUTHENTICATED_SUCCESS,
    AUTHENTICATED_FAIL,
    PASSWORD_RESET_SUCCESS,
    PASSWORD_RESET_FAIL,
    PASSWORD_RESET_CONFIRM_SUCCESS,
    PASSWORD_RESET_CONFIRM_FAIL,
    LOGOUT
} from '../actions/types';

const initialState = {
    access: localStorage.getItem('access'),
    refresh: localStorage.getItem('refresh'),
    isAuthenticated: null,
    user: null
};

export default function(state = initialState, action) {
    const { type, payload } = action;

    switch(type) {

        case AUTHENTICATED_SUCCESS:
            return {
                ...state,
                isAuthenticated: true
            }

        case LOGIN_SUCCESS:
            localStorage.setItem('access', payload.access);
            return {
                ...state,
                isAuthenticated: true,
                access: payload.access,
                refresh: payload.refresh
            }

        case SIGNUP_SUCCESS:
            return {
                ...state,
                isAuthenticated: false
            }

        case USER_LOADED_SUCCESS:
            return {
                ...state,
                user: payload
            }

        case AUTHENTICATED_FAIL:
            return {
                ...state,
                isAuthenticated: false
            }
            
        case USER_LOADED_FAIL:
            return {
                ...state,
                user: null
            }
        
        case LOGIN_FAIL:
        case SIGNUP_FAIL:
        case LOGOUT:
            localStorage.removeItem('access');
            localStorage.removeItem('refresh');
            return {
                ...state,
                access: null,
                refresh: null,
                isAuthenticated: false,
                user: null
            }
            
        // This is just to check that the correct type is being dispatched when doing the action with the tool ReduxDevTools
        case PASSWORD_RESET_SUCCESS:
        case PASSWORD_RESET_FAIL:
        case PASSWORD_RESET_CONFIRM_SUCCESS:
        case PASSWORD_RESET_CONFIRM_FAIL:
        case ACTIVATION_SUCCESS:
        case ACTIVATION_FAIL:
            return {
                ...state
            }

        default:
            return state
    }
};

If someone can check my problem and help me I would appreciate it a lot since this is my Final Degree Work and I have lost a lot of time with this issue already, thanks to everyone who is reading this.



Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source