My live site decided to throw a 403 Forbidden error yesterday on authenticated users when calling an Ajax API and I've trying to troubleshoot with no success. The localhost on my machine works fine when DEBUG = True in my settings.py, but the same code throws the following error:
HTTP 403 Forbidden
Allow: GET, HEAD, OPTIONS
Content-Type: application/json
Vary: Accept
{
"detail": "Authentication credentials were not provided."
}
My rest framework setting in settings.py:
## REST framework default permissions
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.IsAuthenticated',
]
}
Since the browsable API requires SessionAuthentication, I tried the following with no success:
## REST framework default permissions
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.SessionAuthentication',
),
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.IsAuthenticated',
]
}
I did look at Django Rest Framework Docs and it seems to suggest that if my user is logged in, the Ajax calls after login should work fine. Am I missing something? Really appreciate your input
UPDATE 1:
When I run the command:
sudo journalctl -u gunicorn -n 25
One of the things I see is gunicorn[820]: Session data corrupted
I did restart the server, hoping that by logging back in, the new session data will be generated, but the same message is displayed. The logged in user is still not able to view the data the ajax call is trying to fetch. How do I resolve the sessions data corrupted message. I am guessing this affects the DRF authenticating the request
My problem was with the migrations and so I ended up backing up my database, deleting all tables, recreating the tables using Django migrations. Some migration file must have been not in sync for some reason. I really don't know if I messed up manually manipulating something or if it was a bug. Anyways, water under the bridge...
Related
Current Setup: I've got a Django application behind gunicorn running on Cloud Run. Since the region it is deployed in does not support Custom Domains, I have a firebase hosting setup with the following code:
{
"hosting": {
"ignore": [
"firebase.json",
"**/.*",
"**/node_modules/**"
],
"rewrites": [{
"source": "/**",
"run": {
"serviceId": "website",
"region": "ap-south1"
}
}]
}
}
The relevant settings in settings.py:
CSRF_TRUSTED_ORIGINS = ['.<domain>.com']
CSRF_COOKIE_SECURE = True
SESSION_COOKIE_SECURE = True
The problem: However, the login form on /admin does not work if I access the site using my domain name https://doman.com/admin even though it works fine if I use the Cloud Run endpoint https://endpoint-uw.a.run.app.
Faulty behaviour: When accessing it from my domain, the login page shows up, I enter my credentials and log in, it adds the relevant cookies to my browser but then it redirects me back to the login page.
Could it be that since the URL is being rewritten by firebase django is expecting a cookie from uw.a.run.app? I tried adding the setting SESSION_COOKIE_DOMAIN = '.<domain>.com' but that did not fix it either, it just made the Cloud Run endpoint stop working as well.
Any advice on how to fix this or how to diagnose what is going wrong would be much appreciated, thanks!
The relevant settings in settings.py:
SESSION_COOKIE_NAME = "__session"
as firebase send cookie in the name "__session"
I had a pet-api (testing api) without authentication. I'm trying to learn how to implement oath2 to add security to my app.
I'd like to access the models of my app through a request call using
the API but also through the Django Admin Panel.
I'm following this tutorial: https://medium.com/#halfspring/guide-to-an-oauth2-api-with-django-6ba66a31d6d
for setting up: django-oauth-toolkit
Tutorial says I should add this code to settings:
AUTHENTICATION_BACKENDS = (
'django.contrib.auth.backends.ModelBackend' # To keep the Browsable API
'oauth2_provider.backends.OAuth2Backend',
)
But when I run server, and try to access /admin, I get:
ModuleNotFoundError at /admin/login/
No module named 'django.contrib.auth.backends.ModelBackendoauth2_provider'; 'django.contrib.auth.backends' is not a package
If I comment:
# 'django.contrib.auth.backends.ModelBackendoauth2_provider';
I can access the interface for the login, but says my user or password are wrong (they are not).
Commenting both lines I can access the admin panel without problems:
#AUTHENTICATION_BACKENDS = (
# 'django.contrib.auth.backends.ModelBackend' # To keep the Browsable API
# 'oauth2_provider.backends.OAuth2Backend',
#)
There is an error in code,
AUTHENTICATION_BACKENDS = (
'django.contrib.auth.backends.ModelBackend' # To keep the Browsable API
'oauth2_provider.backends.OAuth2Backend',
)
There is a comma (,) missing after 'django.contrib.auth.backends.ModelBackend' so it is taking both lines as single line, as you can see in the error.
So you needed to do just
AUTHENTICATION_BACKENDS = (
'django.contrib.auth.backends.ModelBackend', # To keep the Browsable API
'oauth2_provider.backends.OAuth2Backend',
)
Now it will work...
It's okay, mine is working good now without it. I am also following that guide. Just continue http://127.0.0.1:8000/o/applications.
BTW, I am also commenting the ALLOWED_HOSTS = ['0.0.0.0'] and on the users/views.py, I changed all the http://0.0.0.0:8000 to http://127.0.0.1:8000.
And I now get these:
{
"access_token": "C2qukd1zWz9aGSp652qbnpYjoT6ZRx",
"expires_in": 36000,
"token_type": "Bearer",
"scope": "read write",
"refresh_token": "UoI0r9J09F3kcXGO1q3KsYoGHQ9DBw"
}
I made an Angular application able use an online api to get a json and do stuff.
But, although the json is the same, if I try to change only the url of the json by setting a local url of a server written in django, angular would seem not to connect anymore ...
My question is, why if with an online cloud server works, with a local one wouldn't?
I tried making this server "on cloud" opening the router's port, also setting up a ddns, and using postman or a browser it seems to work, but when i try to connect with angular it still doesn't get the data...
I am sure 100% that the server answer, with the right json data, because django prints on console that he received a HTTP GET request :
http://i.imgur.com/TIQnIcR.png
I remind you that the HTTP angular request worked with another api, but i will still show up some code :
export class ProdottoService {
private prodotti: Array<ProdottoModel>;
constructor(private httpClient: HttpClient) {
this.prodotti = new Array<ProdottoModel>();
var url:string = "https://gist.githubusercontent.com/saniyusuf/406b843afdfb9c6a86e25753fe2761f4/raw/523c324c7fcc36efab8224f9ebb7556c09b69a14/Film.JSON";
var local_url:string = "http://127.0.0.1:8000/films/?format=json";
httpClient.get(local_url)
.subscribe((films : Array<Object> ) => {
films.forEach((film => {
this.prodotti.push(new ProdottoModel(film));
}));
}
);
}
getProdotti(): Array<ProdottoModel> {
return this.prodotti;
}
}
Result using external api :
http://i.imgur.com/MT7xD9c.png
Thanks in advace for any help :3
-- EDIT -- IS A CORS ISSUE
In django settings.py file :
CORS_ORIGIN_WHITELIST = (
'localhost:8000',
'127.0.0.1:4200'
)
INSTALLED_APPS = (
...
'corsheaders',
...
)
MIDDLEWARE = [ # Or MIDDLEWARE_CLASSES on Django < 1.10
...
'corsheaders.middleware.CorsMiddleware',
'django.middleware.common.CommonMiddleware',
...
]
But i don't know if there's a way to set-up CORS settings in Angular
Based on feedback, this is a CORS issue. Your Django server is getting the requests and responding because localhost is an implicitly trusted domain in Django's dev environment, but it isn't configured properly to set the cross origin header, so the browser is not allowing your app to see the response because the server hasn't authorized the domain explicitly.
The problem here is that you've set CORS white list like this:
CORS_ORIGIN_WHITELIST = ( 'localhost:8000', '127.0.0.1:4200' )
it needs to be like this:
CORS_ORIGIN_WHITELIST = ( 'localhost:4200' )
angular runs on localhost, not 127.0.0.1, even though that's what localhost is an alias for, your browser still differentiates them for CORS. Also, you do not need to whitelist the domain your serving off of, that's not crossing any origin as it's the same origin.
In your screenshot, it looks like the response is type "document..." something. The simplest fix, with only the code here, is to convert the string to JSON.
JSON.parse(films)
This is the wrong answer, because it's being set to that because of the CORS issue in the other answers.
Summary
I am looking to use Dropbox SSO functionality by using the authentication from a Django site. Note that I'm not looking to use SAML as a backend for my Django site.
Resources
1) Dropbox Custom SSO help page: https://www.dropbox.com/en/help/1921#custom
2) Creating a SAML response: https://robinelvin.wordpress.com/2009/09/04/saml-with-django/
3) Struggled to find any examples from Google of people doing this kind of SSO. Lots of links about people using SAML as a Django backend.
Question
In the dropbox admin settings I can add my X509 certificate and the login link. This means that when you try to login into Dropbox using SSO it nicely forwards you to my Django site's login page using a GET request with a SAMLRequest in the querystring.
However, my understanding is that I now need to, once the user is authenticated on the Django site, fire a POST request back to Dropbox at their SAML login link with a SAMLResponse in the post data. Using the second resource above I believe I can create the SAMLResponse xml but I am unsure how to redirect the user to the dropbox SAML login link with the SAML data from my Django view.
Any help much appreciated.
Managed to get the functionality I needed using django-saml2-idp https://github.com/peopledoc/django-saml2-idp
Good documentation on installing here: https://github.com/peopledoc/django-saml2-idp/blob/master/doc/INSTALL.txt
Settings in the Dropbox Admin console required the X509 certificate and then the login url set to: https://****.com/idp/login
Note that I had issues installing the M2Crypto dependency so used an Ubuntu package via:
sudo apt-get install python-m2crypto
Additionally I'm using Django 1.9.6 so needed to make overrides to the views.py, urls.py, and registry.py files to make them compatible (various import statements needed updating and the urls changed to the new list format rather than using patterns).
Created a Dropbox Processor as follows:
import base64
import zlib
from saml2idp import base
from saml2idp.xml_render import _get_assertion_xml
def get_assertion_dropbox_xml(parameters, signed=False):
return _get_assertion_xml(ASSERTION_DROPBOX, parameters, signed)
ASSERTION_DROPBOX = (
'<saml:Assertion xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" '
'ID="${ASSERTION_ID}" '
'IssueInstant="${ISSUE_INSTANT}" '
'Version="2.0">'
'<saml:Issuer>${ISSUER}</saml:Issuer>'
'${ASSERTION_SIGNATURE}'
'${SUBJECT_STATEMENT}'
'<saml:Conditions NotBefore="${NOT_BEFORE}" NotOnOrAfter="${NOT_ON_OR_AFTER}">'
'<saml:AudienceRestriction>'
'<saml:Audience>${AUDIENCE}</saml:Audience>'
'</saml:AudienceRestriction>'
'</saml:Conditions>'
'<saml:AuthnStatement AuthnInstant="${AUTH_INSTANT}"'
'>'
'<saml:AuthnContext>'
'<saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:Password</saml:AuthnContextClassRef>'
'</saml:AuthnContext>'
'</saml:AuthnStatement>'
'${ATTRIBUTE_STATEMENT}'
'</saml:Assertion>'
)
class Processor(base.Processor):
def _decode_request(self):
"""
Decodes _request_xml from _saml_request.
"""
self._request_xml = zlib.decompress(base64.b64decode(self._saml_request), -15)
def _format_assertion(self):
self._assertion_xml = get_assertion_dropbox_xml(self._assertion_params, signed=False)
Which you register in your settings.py file as follows:
SAML2IDP_CONFIG = {
'autosubmit': True,
'certificate_file': '/****/certificate.pem',
'private_key_file': '/****/private-key.pem',
'issuer': 'https://www.****.com',
'signing': True,
}
sampleSpConfig = {
'acs_url': 'https://www.dropbox.com/saml_login',
'processor': 'dropbox.Processor',
}
SAML2IDP_REMOTES = {
'sample': sampleSpConfig,
}
Works like a dream. Hope this helps somebody out there.
Would appreciate someone showing me how to make a simple POST request using JSON with Django REST framework. I do not see any examples of this in the tutorial anywhere?
Here is my Role model object that I'd like to POST. This will be a brand new Role that I'd like to add to the database but I'm getting a 500 error.
{
"name": "Manager",
"description": "someone who manages"
}
Here is my curl request at a bash terminal prompt:
curl -X POST -H "Content-Type: application/json" -d '[
{
"name": "Manager",
"description": "someone who manages"
}]'
http://localhost:8000/lakesShoreProperties/role
The URL
http://localhost:8000/lakesShoreProperties/roles
DOES work with a GET request, and I can pull down all the roles in the database, but I can not seem to create any new Roles. I have no permissions set. I'm using a standard view in views.py
class RoleDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = Role.objects.all()
serializer_class = RoleSerializer
format = None
class RoleList(generics.ListCreateAPIView):
queryset = Role.objects.all()
serializer_class = RoleSerializer
format = None
And in my urls.py for this app, the relevant url - view mappings are correct:
url(r'^roles/$', views.RoleList.as_view()),
url(r'^role/(?P<pk>[0-9]+)/$', views.RoleDetail.as_view()),
Error message is:
{
"detail": "CSRF Failed: CSRF token missing or incorrect."
}
What is going on here and what is the fix for this? Is localhost a cross site request? I have added #csrf_exempt to RoleDetail and RoleList but it doesn't seem to change anything. Can this decorator even be added to a class, or does it have to be added to a method?
Adding the #csrf_exempt decorate, my error becomes:
Request Method: POST
Request URL: http://127.0.0.1:8000/lakeshoreProperties/roles/
Django Version: 1.5.1
Exception Type: AttributeError
Exception Value:
'function' object has no attribute 'as_view'
Then I disabled CSRF throughtout the entire app, and I now get this message:
{"non_field_errors": ["Invalid data"]} when my JSON object I know is valid json. It's a non-field error, but I'm stuck right here.
Well, it turns out that my json was not valid?
{
"name": "admin",
"description": "someone who administrates"
}
vs
[
{
"name": "admin",
"description": "someone who administrates"
}
]
Having the enclosing brackets [], causes the POST request to fail. But using the jsonlint.com validator, both of my json objects validate.
Update: The issue was with sending the POST with PostMan, not in the backend. See https://stackoverflow.com/a/17508420/203312
CSRF is exempted by default in Django REST Framework. Therefore, curl POST request works fine. POSTMAN request call returned CSRF incorrect because POSTMAN included csrf token if it is found in Cookies. You can solve this by cleaning up Cookies.
It's from your REST Framework settings. in your settings.py file, your REST_FRAMEWORK should have the following.
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.TokenAuthentication',
),
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.AllowAny',
),
}
This will set your REST Framework to use token authentication instead of csrf authentication. And by setting the permission to AllowAny, you can authenticate only where you want to.
You probably need to send along the CSRF token with your request. Check out https://docs.djangoproject.com/en/1.7/ref/contrib/csrf/#csrf-ajax
Update: Because you've already tried exempting CSRF, maybe this could help (depending on which version of Django you're using): https://stackoverflow.com/a/14379073/977931
OK, well now of course I take back what I said. CSRF does work as intended.
I was making a POST request using a chrome plugin called POSTMAN.
My POST request fails with CSRF enabled.
But a curl POST request using
curl -X POST -H "Content-Type: application/json" -d '
{
"name": "Manager",
"description": "someone who manages"
}' http://127.0.0.1:8000/lakeshoreProperties/roles/
works fine...
I had to take off the braces, i.e., [], and make sure there is a slash after the 's' in roles, i.e., roles/, and csrf enabled did not throw any errors.
I'm not sure what the difference between calling using POSTMAN is vs using curl, but POSTMAN is run in the web browser which is the biggest difference. That said, I disabled csrf for the entire class RoleList but one identical request works with Curl, but fails with POSTMAN.
To give an update on current status, and sum up a few answers:
AJAX requests that are made within the same context as the API they are interacting with will typically use SessionAuthentication. This ensures that once a user has logged in, any AJAX requests made can be authenticated using the same session-based authentication that is used for the rest of the website.
AJAX requests that are made on a different site from the API they are communicating with will typically need to use a non-session-based authentication scheme, such as TokenAuthentication.
Therefore, answers recommending to replace SessionAuthentication with TokenAuthentication may solve the issue, but are not necessarily totally correct.
To guard against these type of attacks, you need to do two things:
Ensure that the 'safe' HTTP operations, such as GET, HEAD and OPTIONS cannot be used to alter any server-side state.
Ensure that any 'unsafe' HTTP operations, such as POST, PUT, PATCH and DELETE, always require a valid CSRF token.
If you're using SessionAuthentication you'll need to include valid CSRF tokens for any POST, PUT, PATCH or DELETE operations.
In order to make AJAX requests, you need to include CSRF token in the HTTP header, as described in the Django documentation.
Therefore, it is important that csrf is included in header, as for instance this answer suggests.
Reference: Working with AJAX, CSRF & CORS, Django REST framework documentation.
As you said your URL was
http://localhost:8000/lakesShoreProperties/roles
Postman has some issues with localhost.
Sending the POST to 127.0.0.1:8000/your-api/endpoint instead did the trick for me.
the old Postman is having a problem with csrf tokens because it does not working with cookies.
I suggest for you to switch to the new version of postman, it works with cookies and you will not face this problem again.
if you have set AllowAny permission and you facing with csrf issue
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.AllowAny'
]
}
then placing following in the settings.py will resolve the issue
REST_SESSION_LOGIN = False