django admin login suddenly demanding csrf token - django

I was logging into my django admin console easily a few minutes ago. I must have changed something somewhere that caused this error when logging in as superuser:
Forbidden (403)
CSRF verification failed. Request aborted.
This error caught me off guard as I was logging in all night. Why would I suddenly need a csrf token for admin login? You would think the sign in form already has that. This is my admin.py:
from django.contrib import admin
from accounts.models import Image, Category, UserProfile
class ImageAdmin(admin.ModelAdmin):
list_display = ["__unicode__", "title", "created"]
admin.site.register(Image, GenericImageAdmin)
class CategoryAdmin(admin.ModelAdmin):
list_display = ["category"]
admin.site.register(Category, CategoryAdmin)
admin.site.register(UserProfile)

for new users facing this issue after upgrading to Django +4.0 you need to add CSRF_TRUSTED_ORIGINS=['https://*.YOUR_DOMAIN.COM'] to settings.py
thanks to the below answer:
https://stackoverflow.com/a/70326426/2259546

Admin login normally does require a csrf token, but that's normally all taken care for you.
Check your browser's cookies to see if there is a csrf token present
Try clearing cookies and refreshing
If you are using Django 4.0, you may to add this line to your settings.py file: CSRF_TRUSTED_ORIGINS = ['https://*.mydomain.com','https://*.127.0.0.1'] (making the appropriate changes). In 4.0, they started checking the origin header unlike in previous versions. Thanks to this answer for suggesting this solution.
Check to make sure you have django.middleware.csrf.CsrfViewMiddleware in your middleware
Check that you're either on https or you have CSRF_COOKIE_SECURE=False (which is the default) in settings, otherwise your csrf cookie exists but won't be sent. Purge your cookies after changing CSRF_COOKIE_SECURE.

This error was appearing for me when I had not set CSRF_COOKIE_DOMAIN in my settings_local but it was set in my main settings.py.
In my case I set it to the local host eg
CSRF_COOKIE_DOMAIN = '127.0.0.1'

Add a csrf token to your context in the login view and in your template add in the hidden div for the csrf token. Ensure you have django.middleware.csrf.CsrfViewMiddleware in the middleware section in your settings.py.
Then add #csrf_protect to your views to do with login. It is also possible you tried to login with incorrect credentials - you need #csrf_protect on the logout view in your app's views.py you call on the appropriate uri for login/logout etc. in urls.py also. My logout simply calls logout(request) then calls HttpResponseRedirect('') which is probably not perfect but it does me for my needs for now.

As a security measure, I had CSRF_COOKIE_SECURE = True in my settings. Trying to log into admin via localhost where there isn't HTTPS threw the forbidden error.
Set it to False to get it working on localhost

This could also happen when you are already logged in into your website hosted on a url different than admin. And then try to login into your admin panel in a new tab.
Try to open the admin panel in a different window.

Try opening your site in incognito mode.
There is a good chance that it could be your browser cookie, the above test will iron out that possibility.

I used to have the same problem every time when I was using my default environment, and then using a virtual environment worked for me. It works every time. If you don't know how to create a virtual environment, here's how you do it:
Just create a virtual environment in your project's directory by
running the command virtualenv theNameYouWannaGiveYourEnvironment.
Then activate your virtual environment by using
theNameYouWannaGiveYourEnvironment/bin/activate(on Linux, I think it works for Mac Os too, but it's different for Windows).
After that, just install Django by pip install django and all the other requirements for your application to run.
Alternatively, you can also use Anaconda to create your virtual environment and install all your requirements. Just refer to this documentation if you wanna use anaconda: https://docs.conda.io/projects/conda/en/latest/user-guide/tasks/manage-environments.html

If you're on Production, make sure that your URL is configured inside ALLOWED_HOST on settings.py

In my case it was solved by changing the setting:
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
to
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'http')

Related

Cannot Login to Django admin with my superuser account when it is on Deployment, showing CSRF error 403 forbidden

I am super newbie on DJango and Programming.
I made a backend server with django and deployed successfully on "render.com".
I can approach to admin login screen but cannot login to it with my superuser ID.
[it worked in runserver perfect but does not work on deployment... showing error below :-( ]
Forbidden (403)
CSRF verification failed. Request aborted.
You are seeing this message because this site requires a CSRF cookie when submitting forms. This cookie is required for security reasons, to ensure that your browser is not being hijacked by third parties.
If you have configured your browser to disable cookies, please re-enable them, at least for this site, or for 'same-origin' requests.
I googled some articles about it. and they told me that I have to add
CSRF_TRUSTED_ORIGINS= ["mydomain"]
on settings.py
Here is the questions...
Q1. Do I have to put my frontend domain or backend domain in "mydomain" ?
Q2. Some articles said that I have to add
CSRF_COOKIE_SECURE = False
in my settings.py too??
Here are my few suggestions kindly note that my use of Django is also limited but the time I have encountered this issue in regards to question one use your frontend domain but also edit your syntax to have single quotation marks than double quotation marks ( I see you're using double quotation marks) as illustrated below:-
CSRF_TRUSTED_ORIGINS = ['https://*.your_domain.com','https://*.127.0.0.1']
About Question two Sometimes it's actually your web browser try using incognito mode and yes you have to set
CSRF_COOKIE_SECURE = False
In your settings file more details on this answer

Getting Django, VUE, CORS and CSRF working with a real world example

I'm really stuck. Here's what I'm trying to do.
KEEP CSRF On. - please don't tell me to turn it off.
I have an API app run by Django and Django Rest Framework
I have a frontend app run by Vue
I have installed django-cors-headers to manage CORS
Everything works great localy. As soon as I move it to production, I start getting CSRF errors. Here's how everything works.
I've seen answers all over that have said everything from turning off CSRF to allowing all for all the things. I want to do this right and not just shut things off and open everything up and end up with a security hole.
So, here's what I have.
Installed:
django-cors-headers
django-rest-framework
drf-nested-routers
... and others
I have the api running at api.websitename.com and the Vue.js app is running at websitename.com.
GET requests work great.
OPTION requests seem to work.
Any risky request does not work.
For my CORS I have 'corsheaders.middleware.CorsMiddleware', installed before my other MIDDLEWARE.
Then my CORS settings are:
CORS_ALLOW_CREDENTIALS = True
CORS_ORIGIN_WHITELIST = (
'*.websitename.com',
)
And my CSRF settings are:
CSRF_TRUSTED_ORIGINS = [
"api.websitename.com",
]
No matter how I play with these, I end up with a CSRF token error.
I've tried the approach of doing something like this in my Vue App.vue file:
mounted () {
this.getCSRFToken()
},
methods: {
getCSRFToken () {
return axios.get('token/').then(response => {
axios.defaults.headers.common['x-csrftoken'] = Cookies.get('csrftoken')
}).catch(error => {
return Promise.reject(error.response.data)
})
}
}
The idea being that I get a CSRF token as soon as the APP loads in the browser. But even with that, I'm getting failed CSRF token errors when the app tries to do anything except a GET or OPTION.
Here's the view that returns the token incase youre curios:
class CSRFTokenView(APIView):
permission_classes = (permissions.AllowAny,)
#method_decorator(ensure_csrf_cookie)
def get(self, request):
return HttpResponse()
I realize I might be mixing problems here, but any suggestions that could help me trouble shoot this are welcome.
First of all you want to use SessionAuthentication:
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.SessionAuthentication',
)
}
This will enforce CSRF, except for anonymous users (more on this in a bit). For browser frontends the easiest solution is to have both the (browser) frontend and backend under the same domain - this lets you avoid CORS - as suggested by comments above. If you have other clients then just go with tokens (DRF tokens or JWT) - but these are not safe for browser usage due to the danger of XSS attacks (storing tokens in localStorage is inherently insecure).
As you are using axios, CSRF setup is dead easy:
import axios from 'axios'
axios.defaults.xsrfHeaderName = 'X-CSRFToken'
axios.defaults.xsrfCookieName = 'csrftoken'
So you should have safe sessions with CSRF enforced. Almost. To quote the linked page above:
Warning: Always use Django's standard login view when creating login pages. This will ensure your login views are properly protected.
CSRF validation in REST framework works slightly differently to standard Django due to the need to support both session and non-session based authentication to the same views. This means that only authenticated requests require CSRF tokens, and anonymous requests may be sent without CSRF tokens. This behaviour is not suitable for login views, which should always have CSRF validation applied.
This is icky - you either have to just use Django server-side views which makes your SPA design somewhat more complicated or recreate login and other auth views in DRF, with the caveat of using the #csrf_protect method decorator to enforce CSRF on these "anonymous" views. Obviously such views will break for token-using clients so you probably want to use different endpoints for these (maybe re-using the same base classes). So your browser login uses /auth/browser/login/ and your mobile login /auth/mobile/login/, the former wrapped using #csrf_protect.
Recreating login and other auth views from scratch should be done carefully after studying the contrib auth source code; for vanilla requirements I would recommend pre-existing solutions like django-rest-auth and django-all-auth. The django-rest-auth package however is not well designed for browser frontends and forces the usage of token generation, plus you would need to wrap the views as described above. On the other hand, all-auth provides AJAX responses for JS clients and might be a better bet.
By far the easiest way to resolve this is to serve everything from the same domain. You can have your CDN or proxy direct /api calls to one server and the rest to the frontend server. This way there is no need to worry about CORS at all.
To get this working, I think you're just missing withCredentials = true in AXIOS configuration. Django requires the CSRF cookie to be sent and cookies are not sent over cross origin requests when withCredentials is not set.
axios.interceptors.request.use(function (config) {
config.withCredentials = true
return config
})
Another setting that might be missing is Djano's SESSION_COOKIE_DOMAIN. You should set it like this:
SESSION_COOKIE_DOMAIN=".mywebsite.com"
That first dot is important because it tells Django and then the web browser to use the cookie for *.mywebsite.com including api.mywebsite.com.
If it all still fails, I suggest setting a breakpoint on Django's CSRF middleware to see what's missing to make it work.

Clould9's Django out of the box: Admin page CSRF :443 error

Clould9's Django out of the box gives CSRF error when I attempt to login to the admin page.
Reason given is:
- Forbidden (403)
- CSRF verification failed. Request aborted.
- Referer checking failed - https://mysite.c9.io/admin/login/?next=/admin/ does
not match https://mysite.c9.io:443/
No changes were made to the instance, other than creating a superuser.
Commenting out setting.py MIDDLEWARE_CLASSES 'django.middleware.csrf.CsrfViewMiddleware' did not fix the issue.
Current workaround is through setattr(request, '_dont_enforce_csrf_checks', True), found here:
Django CSRF framework cannot be disabled and is breaking my site
The problem seems to be the way Cloud9 treats HTTPS on port 443. Is there a way to fix this without disabling CSRF for the whole site? I also seem to be sandboxed out of django-admin.py, so I can't limit the hack to just the admin page.
I have opened a ticket for this issue with the cloud9 support #eff M. They are great guys out there and great IDE as well with great support.
Meanwhile try this workaround for the time being:
access your admin page with http://mysite.c9.io/admin/login/?next=/admin/ and not with https://mysite.c9.io/admin/login/?next=/admin/ and see if you can login. it worked well on my side.

403-page in Django

I use Django 1.4. I created 403.html file in the same directory as 404.html (404 error page works fine). Yes, I read this. Then I turn off cookies in my browser, try to login and see the default 403-error page, not mine 403.html page:
Forbidden (403)
CSRF verification failed. Request aborted.
More information is available with DEBUG=True.
I restarted Apache, but it doesnt help.
How to fix it? Thanks
This is not the default 403-error page. You are seeing this message because the CSRF middleware does not work when cookies are disabled.
Your custom 403 template has no effect because the CSRF middleware does not use the general 403 view, but the view defined by the setting CSRF_FAILURE_VIEW, which is defined in django.conf.global_settings as django.views.csrf.csrf_failure. As you can see in the source, the message you are seeing is hardcoded in the view.
You could create your own CSRF_FAILURE_VIEW, but that is probably not what you want. I suggest you leave everything as it is and just delete the cookies or use another browser to test as unauthenticated user.

Can I use HTTP Basic Authentication with Django?

We have a website running on Apache, access to which has a number of static pages protected via HTTP Basic authentication.
I've written a new part of the site with Django using Django's built in support for user management.
The problem I have is that users have to log in once via the HTTP Basic authentication and then again using a Django login form. This both clumsy and very confusing for users.
I was wondering if anyone had found a way to make Django log a user in using the HTTP Basic authentication information.
I not expecting to pass a password to Django, but rather if a user dave has been authenticated by Apache then they should be automatically logged into Django as dave too.
(One option would be to make Apache and Django share a user store to ensure common usernames and passwords but this would still involve two login prompts which is what I'm trying to avoid.)
For just supporting basic auth on some requests (and not mucking with the web server -- which is how someone might interpret your question title), you will want to look here:
http://www.djangosnippets.org/snippets/243/
This has been added to the Django 1.3 release. See more current documentation for this here:
http://docs.djangoproject.com/en/dev/howto/auth-remote-user/
Do check out Oli's links. You basically see the authenticated username as verified by Basic HTTP Authentication in Django by looking at request.META['REMOTE_USER'].
Update: Tested the proposed patch for ticket #689, which is available up-to-date in telenieko's git repository here. It applies cleanly at least on revision 9084 of Django.
Activate the remote user authentication backend by
adding the RemoteUserAuthMiddleware after AuthenticationMiddleware
adding the setting AUTHENTICATION_BACKENDS = ('django.contrib.auth.backends.RemoteUserAuthBackend',)
If you use lighttpd and FastCGI like I do, activate mod_auth, create credentials for a test user (I called it testuser and set 123 as the password) and configure the Django site to require basic authentication.
The following urls.py can be used to test the setup:
from django.conf.urls.defaults import *
from django.http import HttpResponse
from django.contrib.auth.models import User
urlpatterns = patterns('',
url(regex='^$',
view=lambda request: HttpResponse(repr(request), 'text/plain')),
url(regex='^user/$',
view=lambda request: HttpResponse(repr(request.user), 'text/plain')),
url(regex='^users/$',
view=lambda request: HttpResponse(
','.join(u.username for u in User.objects.all()),
'text/plain')),
)
After reloading lighty and the Django FCGI server, loading the root of the site now asks for authentication and accepts the testuser credentials, and then outputs a dump of the request object. In request.META these new properties should be present:
'AUTH_TYPE': 'Basic'
'HTTP_AUTHORIZATION': 'Basic dGVzdHVzZXI6MTIz'
'REMOTE_USER': 'testuser'
The /user/ URL can be used to check that you're indeed logged in as testuser:
<User: testuser>
And the /users/ URL now lists the automatically added testuser (here the admin user I had created when doing syncdb is also shown):
admin,testuser
If you don't want to patch Django, it's trivial to detach the RemoteUserAuthBackend and RemoteUserAuthMiddleware classes into a separate module and refer to that in the Django settings.
Yes you can use basic autorization with django as something similar:
def post(self, request):
auth_header = request.META.get('HTTP_AUTHORIZATION', '')
token_type, _, credentials = auth_header.partition(' ')
import base64
expected = base64.b64encode(b'<username>:<password>').decode()
if token_type != 'Basic' or credentials != expected:
return HttpResponse(status=401)
authorization success flow code ...
request.META contains key HTTP_AUTHORIZATION in which your Autorization is present.
In case if you are using apache with modWSGI, the key HTTP_AUTHORIZATION might not be present. You need to add below line in your WSGI config
WSGIPassAuthorization On
Refer this detailed answer:
Passing apache2 digest authentication information to a wsgi script run by mod_wsgi
Hope it is useful for someone who is wondering why HTTP_AUTHORIZATION key is not present
There is httpauth.py. I'm still a complete newb with Django so I've no idea how it fits in exactly, but it should do what you're looking for.
Edit: here's a longer bug thread on the subject.
Because django can be run in several ways, and only modpython gives you close integration with Apache, I don't believe there is a way for django to log you in basic on Apache's basic auth. Authentication should really be done at the application level as it'll give you much more control and will be simpler. You really don't want the hassle of sharing a userdata between Python and Apache.
If you don't mind using a patched version of Django then there is a patch at http://www.djangosnippets.org/snippets/56/ which will give you some middleware to support basic auth.
Basic auth is really quite simple - if the user isn't logged in you return a 401 authentication required status code. This prompts the browser to display a login box. The browser will then supply the username and password as bas64 encoded strings. The wikipedia entry http://en.wikipedia.org/wiki/Basic_access_authentication is pretty good.
If the patch doesn't do what you want then you could implement basic auth yourself quite quickly.
This seems to be a task for custom AuthenticationBackend - see Django documentation on this subject, djangosnippets.org has some real-life examples of such code (see 1 or 2) (and this is not really a hard thing).
AuthenticationBackend subclasses have to have only 2 methods defined and their code is pretty straightforward: one has to return User object for user ID, the second has to perform credentials check and return User object if the credentials are valid.