Encountering a 302 redirect issue when submitting a form in React/Django app as API call gets redirected
In my Django/React app I want a user to be signed in to be able to successfuly submit a request to an API.
I successfully login with google and I believe I am passing all of the correct data to the backend api for it to be called.
I have configured CORS, ensured my CSRF tokens match, am using @login_required on my Django view, use "credentials: 'include'" in the api call, and have exhausted both ChatGPT and my own internet searching to solve the problem.
From my Conversation.jsx file I want to call /api/ask_new_york from app/views.py. Even when logged in I get a 302 response.
Here is all of what I think is the relevant code and the output:
settings.py:
MIDDLEWARE = [
'corsheaders.middleware.CorsMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'allauth.account.middleware.AccountMiddleware',
]
# CORS settings
CORS_ALLOW_ALL_ORIGINS = True
CORS_ALLOW_CREDENTIALS = True
# CSRF settings
CSRF_TRUSTED_ORIGINS = [
'http://localhost:3000',
'http://localhost:8000',
'http://127.0.0.1:8000',
'http://localhost'
]
# Disable CSRF cookies only if needed
CSRF_COOKIE_SECURE = False
LOGIN_REDIRECT_URL = '/'
LOGIN_URL = '/login/'
LOGOUT_REDIRECT_URL = '/'
app/views.py:
@csrf_exempt
def get_csrf_token(request):
csrf_token = get_token(request)
return JsonResponse({'csrf_token': csrf_token})
@login_required
def ask_new_york(request):
if request.method == 'POST':
try:
data = json.loads(request.body.decode('utf-8'))
input_value = data.get('input_value'
users/views.py: This handles the users creation.(this is the whole file)
from django.contrib.auth import get_user_model
from django.conf import settings
from django.http import JsonResponse
from django.views.decorators.csrf import csrf_exempt
from google.oauth2 import id_token
from google.auth.transport import requests as google_requests
from rest_framework.decorators import api_view, permission_classes
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from .serializers import UserSerializer
from django.db import IntegrityError
User = get_user_model()
@api_view(['POST'])
@csrf_exempt
def google_login(request):
if request.method == 'POST':
try:
token = request.data.get('token')
if not token:
return JsonResponse({'error': 'No token provided'}, status=400)
# Verify the token using Google's API
idinfo = id_token.verify_oauth2_token(token, google_requests.Request(), settings.GOOGLE_CLIENT_ID)
# If token is valid, get the user info
email = idinfo['email']
name = idinfo.get('name', '')
# Check if the user exists, if not, create a new user
user, created = User.objects.get_or_create(email=email, defaults={'username': email, 'first_name': name})
if created:
user.set_unusable_password() # If user is created, ensure no password is set
user.save()
# Return user info
return JsonResponse({'email': user.email, 'name': user.first_name})
except ValueError:
return JsonResponse({'error': 'Invalid token'}, status=400)
except IntegrityError:
# Handle the case where the email already exists
return JsonResponse({'error': 'User with this email already exists'}, status=400)
return JsonResponse({'error': 'Invalid request method'}, status=400)
@api_view(['GET'])
@permission_classes([IsAuthenticated])
def user_detail(request):
user = request.user
serializer = UserSerializer(user)
return Response(serializer.data)
LoginSignUpPage.jsx: Frontend page where user actually logs in. (this is the whole file)
import React, { useContext, useEffect, useState } from 'react';
import { GoogleLogin } from '@react-oauth/google';
import { useNavigate } from 'react-router-dom';
import { jwtDecode } from "jwt-decode";
import AuthContext from './AuthContext';
import axios from 'axios';
import { getCsrfToken } from './csrfToken'; // Import the utility
const LoginSignupPage = () => {
const { setAuthUser } = useContext(AuthContext);
const navigate = useNavigate();
const [csrfToken, setCsrfToken] = useState('');
useEffect(() => {
const token = getCsrfToken(); // Use the centralized function to get CSRF token
setCsrfToken(token);
console.log("CSRF token set as:", token);
}, []);
const handleLoginSuccess = async (response) => {
try {
const decoded = jwtDecode(response.credential); // Decode JWT
const { email, name } = decoded;
// Send token to backend for verification and authentication
const res = await axios.post(
'http://localhost:8000/api/google-login/',
{ token: response.credential },
{
headers: {
'X-CSRFToken': csrfToken, // Include the CSRF token in the headers
},
}
);
const user = {
email: res.data.email,
name: res.data.name,
created_at: res.data.created_at,
};
console.log(user); // Log user info
// Save user data in context or global state
setAuthUser(user);
// Redirect to home page after successful login
navigate('/');
} catch (error) {
console.error('Login Failed:', error);
}
};
const handleLoginFailure = (error) => {
console.log('Login Failed:', error);
};
return (
<div>
<h1>Login</h1>
<GoogleLogin onSuccess={handleLoginSuccess} onError={handleLoginFailure} />
</div>
);
};
export default LoginSignupPage;
Conversation.jsx: Homepage where user submits text to the API handled in handleSubmit. There is some error handling and front end components that execute handleSubmit when clicked in here as well.
import { getCsrfToken } from './csrfToken';
useEffect(() => {
const token = getCsrfToken(); // Use the centralized function to get CSRF token
setCsrfToken(token);
console.log("CSRF token set as:", token);
}, []);
const handleSubmit = async (e) => {
try {
let headers = {
'X-CSRFToken': csrfToken,
'Content-Type': 'application/json',
};
response = await fetch("http://localhost:8000/api/ask_new_york/", {
method: "POST", // Specify POST method
headers: headers,
credentials: 'include', // Ensures cookies are sent with the request
body: JSON.stringify({ input_value: inputValue }),
});
}
csrfToken.js: Gets the csrftoken(this is the whole file)
export const getCookie = (name) => {
const value = `; ${document.cookie}`;
console.log(value)
const parts = value.split(`; ${name}=`);
if (parts.length === 2) return parts.pop().split(';').shift();
};
export const getCsrfToken = () => {
return getCookie('csrftoken'); // Adjust the name if your cookie is named differently
};
In my web console I have the csrfToken.js, LoginSignUpPage.jsx and Conversation.jsx files log the csrftoken they have and they are all the same. When logged in and handleSubmit is executed in Conversation.jsx I get following errors: In Google Chrome:
Error submitting data: SyntaxError: Unexpected token '<', "<!doctype "... is not valid JSON
I believe it is receiving the html of the /login page it wants to redirect to even though the user is logged in. Maybe this is where I am wrong.
In my backend terminal:
[11/Sep/2024 21:35:31] "POST /api/ask_new_york/ HTTP/1.1" 302 0
[11/Sep/2024 21:35:31] "GET /login/?next=/api/ask_new_york/ HTTP/1.1" 200 644.
I do not know why error 302 is happening.
Extra:
[11/Sep/2024 21:35:22] "OPTIONS /api/google-login/ HTTP/1.1" 200 0
[11/Sep/2024 21:35:22] "POST /api/google-login/ HTTP/1.1" 200 47
Google login seems to work fine.