I am currently making a web app that uses Vue.JS for the frontend part and Django for the backend. I'm using django-rest-framework to communicate between the client and the server.
I would like to specify that everything works fine when using Postman for testing my requests
When trying to login or register (those are only the two features I have implemented yet) The server sends back the following response:
"CSRF Failed: Origin checking failed - http://localhost:8080 does not match any trusted origins." Status: 403
The csrftoken cookie is there and I made sure it's sent
Here are some details on my setup
Requests are sent using Axios. In main.js I overrode the following axios.defaults
axios.defaults.baseURL = 'http://localhost:8000';
axios.defaults.xsrfCookieName = 'csrftoken';
axios.defaults.xsrfHeaderName = 'X-CSRFToken';
axios.defaults.withCredentials = true;
And the login request is sent in this method in a Pinia user store:
async login(email, password)
{
try
{
let response = await axios.post('authentication/login/', {email: email, password:
password});
this.setUserData(response.data.id, response.data.email);
}
catch(error)
{
console.log(error);
}
},
Server side:
My User view is written the following way:
def UserAuthenticationView(APIView):
serializer_class = UserSerializer
def get(self, request):
#retrieve currently logged-in user
pass
def post(self, request):
#register new user in database
pass
#api_view(['POST])
def login(request):
# decode request sent as json
body = json.loads(request.body)
# make sure all fields are present
try:
email = body['email']
password = body['password']
except KeyError:
return Response("Email or password are missing", status=400)
# validateEmail() and validatePassword() are custom functions that check the format of both
# fields
if (not validateEmail(email) or not validatePassword(password)):
return Response("Email or password are missing", status=400)
user = authenticate(email=email, password=password)
# check if authentication worked
if user is None:
return Response("Failed to authenticate user", status=400)
else:
# log user on session and return it
login(request, user)
return Response(UserAuthenticationView.serializer_class(user).data)
Inside settings.py I added the following line
CSRF_TRUSTED_ORIGINS = ["http://localhost:8080"]
But because my login view needs to access the body of the request I get an internal server error. From what I understand, the CSRF Middleware reads the body of the request first and I should use csrf_exempt() to prevent this behaviour
So in urls.py I tried the following with no success:
urlpatterns = [
path('', views.UserAuthenticationView.as_view()),
path('login/', csrf_exempt(views.UserAuthenticationView.login)),
What I don't understand either is that APIView is supposed to automatically csrf_exempt all views inside it but my register view, which corresponds to the post() method of UserAuthenticationView doesn't work for the same reason.
Related
I need to sign in a user using firebase from my django app.I have done what I think I needed to do but I seem to be missing something.I am using the pyrebase library.I have created a user on firebase and now I need to sign them in.
I am posting the email and password on Postman and I get the 'idToken' and 'refreshToken', which means the user gets authenticated on firebase.But this only works when I use the drf Token authentication(DEFAULT AUTH CLASSES) and authorization token of a user previously created on django admin. What am I missing so that I can authenticate the user without the drf token authentication?
views.py
config = {
"apiKey": "xxxxxxxxxxxxxxxxxxxxxxxxxxx",
"authDomain": "xxxxx.firebaseapp.com",
"databaseURL": "https://xxxxxxxxx-default-rtdb.firebaseio.com",
"storageBucket": "xxxxxxxxx.appspot.com",
}
firebase = pyrebase.initialize_app(config)
auth = firebase.auth()
class Auth(APIView):
def post(self, request, format=None):
email = "xxxx#gmail.com"
password = "xxxx"
user = auth.sign_in_with_email_and_password(email, password)
return Response(user)
Settings.py
REST_FRAMEWORK = {
"DEFAULT_AUTHENTICATION_CLASSES": (
"rest_framework.authentication.TokenAuthentication",
),
"DEFAULT_PERMISSION_CLASSES": ("rest_framework.permissions.IsAuthenticated",),
}
Yeah so basically, you don't need any authorization in any of the authentication views. Since you have a global default of IsAuthenticated, you need to overwrite the permission_classes in the View.
class Auth(APIView):
permission_classes = []
def post(self, request, format=None):
...
I'm writing a custom Authentication middleware that check the incoming requests for the "Authorization" key in the header, which contains a token.
I'm using this token to check with a third-party (Microsoft Graph) for the validity of the user. MS Graph will respond with an object like below
# the response object
{
'#odata.context': 'https://graph.microsoft.com/v1.0/$metadata#users/$entity',
'businessPhones': ['xxx'],
'displayName': 'xxx',
'givenName': 'xxx',
'id': 'xxx',
'jobTitle': None,
'mail': 'xxx',
'mobilePhone': None,
'officeLocation': None,
'preferredLanguage': 'xxx',
'surname': 'xxx',
'userPrincipalName': 'xxx'
}
EDIT: Adding custom middleware code here:
class AuthenticationMiddleware(MiddlewareMixin):
if not request.user.is_authenticated:
if "Authorization" in request.headers:
# Make a request to MS Graph with the given token
# to get user details and append to request
token = request.headers["Authorization"]
elif "accessToken" in request.GET:
token = request.GET["accessToken"]
else:
token = None
if token:
url = "https://graph.microsoft.com/v1.0/me/"
payload = {}
headers = {"Authorization": "Bearer {}".format(token)}
response = requests.request("GET", url, headers=headers, data=payload)
if response.ok:
request.custom_user = response.json()
else:
request.custom_user = AnonymousUser
else:
request.custom_user = AnonymousUser
Now I want to design this to work just like Django's default authentication backend with proper group and permission. How can I work on a LazyObject to be able to check for user's group membership and permission?
UPDATE
It looks like there's also a custom backend authentication that works like this.
Is it doing the same thing as I'm doing with the middleware?
from django.contrib.auth.backends import BaseBackend
class MyBackend(BaseBackend):
def authenticate(self, request, token=None):
# Check the token and return a user.
...
you should custom an Middleware like the below, and add it to middlewares in settings
class SimpleMiddleware:
def __init__(self, get_response):
self.get_response = get_response
# One-time configuration and initialization.
def __call__(self, request):
# Code to be executed for each request before
# the view (and later middleware) are called.
response = self.get_response(request)
# todo: do something you want in response
return response
see also:https://docs.djangoproject.com/en/3.1/topics/http/middleware/
EDIT:
Is it doing the same thing as I'm doing with the middleware?
no, it's not.
the most different is that
The backend is used for connecting with database, and the middleware is aim to process the request. You can find more examples code in django.middleware package.
and if you want to custome how to save the infomation to database eg: customer the authenticate method, you should customer a backend for the work. otherwise you may custome an middleware to process all of the requests. Because it is easy to customize middleware.
I am trying to have two different redirects...one for normal login and another for redirect after email confirmation
ACCOUNT_EMAIL_CONFIRMATION_AUTHENTICATED_REDIRECT_URL = '/profile'
LOGIN_REDIRECT_URL = '/'
But when I enable login, AUTHENTICATED REDIRECT goes to LOGIN_REDIRECT but when I disable Login it goes to the EMAIL_CONFIRMATION_REDIRECT route.
When I try printing the adapter settings for email_confirmation redirect url below it shows only the LOGIN_REDIRECT
def get_email_confirmation_redirect_url(self, request):
""" The URL to return to after successful e-mail confirmation. """
if request.user.is_authenticated:
if app_settings.EMAIL_CONFIRMATION_AUTHENTICATED_REDIRECT_URL:
return \
app_settings.EMAIL_CONFIRMATION_AUTHENTICATED_REDIRECT_URL
else:
return self.get_login_redirect_url(request)
else:
return app_settings.EMAIL_CONFIRMATION_ANONYMOUS_REDIRECT_URL
I tried overriding this get_email_confirmation_redirect_url in the adapter but still wont work. It is not picking the REDIRECT before I login and reverify.
Since ACCOUNT_EMAIL_CONFIRMATION_AUTHENTICATED_REDIRECT_URL = '/profile' was not working if the user is not logged in, I decided to override DefaultAccountAdapter in Django Allauth. My login was that if the time the user joined the app and the time logged in exceeds a certain threshold, then the redirection would be different. So I created an adapter in my users app as below:
class AccountAdapter(DefaultAccountAdapter):
def get_login_redirect_url(self, request):
expiry = 90 #seconds
assert request.user.is_authenticated
if (request.user.last_login - request.user.date_joined).seconds < expiry:
url = 'profile/'
else:
url = settings.LOGIN_REDIRECT_URL
return resolve_url(url)
I then passed this adapter in my settings.py
I'm working on a DRF API, i will like to consume the API on the same project rather than using Django ORM structure,
I have successfully Login a user and generated a token.
Now i want to restrict a Django View based on a response of an API call
class Login(generic.FormView):
template_name = 'registration/login.html'
form_class = LoginForm
success_url = reverse_lazy('customer_dashboard')
def form_valid(self, form):
parameters = {
'username': str(form.cleaned_data['username']),
'password': str(form.cleaned_data['password']),
}
# Param from LOGIN form posted to API, if token response, means user is authenticated and active
headers = {"Content-Type": 'application/json'}
response = requests.post(str(settings.API_END_POINT + '/api-token-auth/'), json=parameters, headers=headers)
data = response.json()
# response CODE 2xx means a success, if POST request is success, then save USE TOKEN and ID to session
if response.status_code in settings.SUCCESS_CODES:
self.request.session['validated_user_token'] = data['token']
self.request.session['validated_user_id'] = data['user_id']
# get request from all_user end_point and match USER ID from before to list to fetch user details
headers = {"Content-Type": 'application/json', "Authorization": "Token " + settings.API_TOKEN}
response = requests.get(str(settings.API_END_POINT + '/users_api/'), headers=headers)
users = response.json()
print(self.request.session['validated_user_id'])
for user in users:
if user['id'] == self.request.session['validated_user_id']:
messages.success(self.request, 'Hi' + ' ' + user['first_name'] + ' ' + user['last_name'])
else:
messages.error(self.request, 'Invalid Credentials')
return super(Login, self).form_valid(form)
Here i have the username the password passed to an API post request, that authenticate the credentials and return a Token and User_id. Now based on the i will like to restrict the Dashboard(codes below) to those users authenticated above.
class CustomerDashboard(generic.TemplateView):
template_name = 'customer/dashboard.html'
I will like the CustomerDashboard() to be restricted to users that have been authenticated by the API call
I have no idea why you actually made login this complicated. You can use built-in authentication. you can tweak this for any other functionality.
urls.py
from django.contrib.auth import views
urlpatterns = [
path('login/', views.LoginView.as_view(redirect_authenticated_user=True), name='login'),
path('dashboard/', dashboard.Dashboard.as_view(), name='dashboard'),
]
views.py
from django.contrib.auth.mixins import LoginRequiredMixin
class Dashboard(LoginRequiredMixin, generic.TemplateView):
template_name = "dashboard.html"
...
This worked perfectly fine for me.
I am wondering if the request is actually being made via http. In my app I have a test that looks like
class Authenticate(APITestCase):
def setUp(self):
self.client = APIClient()
self.password_for_admin = '123456'
self.admin = User.objects.create_superuser(username='myname', email='email#email.com', password='123456')
self.token = Token.objects.create(user=self.admin)
def test_authenticate(self):
""" comment """
self.client.credentials(HTTP_AUTHORIZATION='Basic ' + base64.b64encode('{}:{}'.format(self.admin.username, self.password_for_admin)))
response = self.client.post('/api/authenticate/')
print response
And in my view I've got:
#api_view(('POST',))
def authenticate(request, format=None):
""" comment """
import pprint
log.debug(pprint.pprint(request))
try:
"asdlfjl"
except Exception, e:
response = "An error occurred, {}".format(e)
return Response(response)
My settings looks like:
INSTALLED_APPS = (
...
'django.contrib.sessions',
)
MIDDLEWARE_CLASSES = (
'django.contrib.sessions.middleware.SessionMiddleware',
...
)
The request is being printed out as None in my log file. I need to get the session. I tried request.session (which was None) and that's what led me to this question.
I figured it out. The server does send a request using the testserver domain. This was sort of a misleading question and the code was wrong. The user is already authenticated using the rest basic backend by the time they reach this view method.
Through much research I found out that the user was being authenticated by rest but the login method doesn't get called by the rest backend. Since login doesn't get called from a rest backend the session is never attached to the request. I changed the authenticate method to login and I simply called login by doing this:
...
#api_view(('POST',))
def login(request, format=None):
try:
from django.contrib.auth import login
if request.user and request.user.is_active:
login(request, request.user)
...
response = ...
else:
response = {}
except Exception, e:
response = "An error occurred, {}".format(e)
return Response(response)