Unable to login subdomain of django tenant, primary domain and admin log in work
I have a multi tenant app using the library django-tenants
. When logging into the core URL with the created superuser, the login page works completely fine. When using the subdomain address to login, the page returns a 500 error
when using the correct username and password that exists and the console says Uncaught (in promise) SyntaxError: Unexpected token '<', " <!doctype "... is not valid JSON
.
When using an login username that doesn't exist, the output is 400 Bad Request
.
The odd part, is I can access the admin panel of a subdomain, log in completely fine, change the URL to go to a part of the app - and the user is now logged in. After being logged in, I can submit forms completely fine and data is being captured, it is solely the login page on a subdomain that has an error submitting.
I am lost since subsequent data forms submit and go correctly, and there are no invalid tokens (from what I see)
views.py
def dual_login_view(request):
"""Authenticate users with both Django Authentication System
and Django Rest Framework"""
if request.method == "POST":
username = request.POST.get("login")
password = request.POST.get("password")
user = authenticate(request, username=username, password=password)
if user is not None:
# Login user and create session
login(request, user)
# Create or get API token
token, created = Token.objects.get_or_create(user=user)
response_data = {"status": "success", "token": token.key}
return JsonResponse(response_data)
else:
return JsonResponse(
{"status": "error", "message": "Username with the password doesn't exist!"}, status=400
)
return JsonResponse(
{"status": "error", "message": "Invalid request method"}, status=405
)
class CustomLoginView(LoginView):
def form_invalid(self, form):
for error in form.errors.values():
for e in error:
messages.error(self.request, e)
return super().form_invalid(form)
class UserLoginAPIView(APIView):
def post(self, request):
if not (request.user and request.user.is_authenticated):
return Response(
{"error": "User not recognized"}, status=status.HTTP_401_UNAUTHORIZED
)
try:
token, created = Token.objects.get_or_create(user=request.user)
return Response({"token": f"Token {token.key}"}, status=status.HTTP_200_OK)
except Exception as e:
print(e)
return Response(
{"error": "Invalid credentials"}, status=status.HTTP_401_UNAUTHORIZED
)
class UserLoginAPIView(APIView):
def post(self, request):
"""Handle user login and return an authentication token."""
username = request.data.get("username")
password = request.data.get("password")
try:
user = MSPAuthUser.objects.get(username=username)
# Verify password
if not user.check_password(password):
raise ValueError("Invalid credentials")
token, created = Token.objects.get_or_create(user=user)
return Response({"token": f"Token {token.key}"}, status=status.HTTP_200_OK)
except ObjectDoesNotExist:
return Response(
{"error": "Invalid credentials"}, status=status.HTTP_401_UNAUTHORIZED
)
except ValueError as e:
return Response({"error": str(e)}, status=status.HTTP_401_UNAUTHORIZED)
login.js
<script>
let form = document.querySelector('form');
form.addEventListener('submit', function(event) {
event.preventDefault();
var formData = new FormData(this);
form.classList.add('was-validated')
if (!form.checkValidity()) {
event.preventDefault()
event.stopPropagation()
return;
}
fetch("{% url 'dual_login' %}", {
method: 'POST',
body: formData,
headers: {
'X-CSRFToken': formData.get('csrfmiddlewaretoken'),
}
}).then(response => response.json())
.then(data => {
if (data.status === 'success') {
// Store the token (e.g., in local storage or a cookie)
localStorage.setItem('token', data.token);
// Redirect or update the UI
window.location.href = '/';
} else {
Swal.fire({
icon: 'error',
title: data.message,
showConfirmButton: false,
timer: 1500,
})
}
});
});
</script>
Check the network tab for API response. Maybe you are trying to parse something that is not JSON as JSON.
The Uncaught (in promise) SyntaxError: Unexpected token '<', " <!doctype "... is not valid JSON
error occurs because the Token
call raises an uncaught exception, as you noticed. Which in turn means the token
object doesn't contain a token, it contains the HTML response displaying the exception response.
And you have rightly deduced that the solution is to fix the Token
call.
how can I use from rest_framework.authtoken.models to query from the proper database schema?
By the time you make the Token
call, you've already authenticated the user so the next step is to look up their tenant association. Then you can use the schema_context()
context manager to direct the query to the correct schema.
from django_tenants.utils import schema_context
from .tenants import Tenant # Or whatever your tenant model is
def dual_login_view(request):
"""Authenticate users with both Django Authentication System
and Django Rest Framework"""
if request.method == "POST":
username = request.POST.get("login")
password = request.POST.get("password")
user = authenticate(request, username=username, password=password)
if user is not None:
# Login user and create session
login(request, user)
# Retrieve tenant information
tenant = user.tenant # Or whatever is appropriate for you to look up the relationship between the user and the tenant
schema_name = tenant.schema_name
# Create or get API token, schema-aware
with schema_context(schema_name):
token, created = Token.objects.get_or_create(user=user)
response_data = {"status": "success", "token": token.key}
return JsonResponse(response_data)
else:
return JsonResponse(
{"status": "error", "message": "Username with the password doesn't exist!"}, status=400
)
return JsonResponse(
{"status": "error", "message": "Invalid request method"}, status=405
)