csrf fail on restAuth with TokenAuthentication and corsheaders - django

I am trying to make auth apis for my django+react project. I have used restauth package. When I hit the api with postman, it is successful (200) on get request but fails on post request due to csrf, I looked on internet and disabled sessionauthentication but still in vain .Here is my settings.py minimal settings for restauth
INSTALLED_APPS = [
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
"django.contrib.sites",
"whitenoise.runserver_nostatic"
]
LOCAL_APPS = [
"users.apps.UsersConfig",
"corsheaders",
]
THIRD_PARTY_APPS = [
"rest_framework",
"rest_framework.authtoken",
"rest_auth_custom",
"rest_auth_custom.registration",
"allauth",
"allauth.account",
"allauth.socialaccount",
"allauth.socialaccount.providers.google",
]
INSTALLED_APPS += LOCAL_APPS + THIRD_PARTY_APPS
MIDDLEWARE = [
'corsheaders.middleware.CorsMiddleware',
"django.middleware.security.SecurityMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
"django.middleware.common.CommonMiddleware",
"django.middleware.csrf.CsrfViewMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
"django.contrib.messages.middleware.MessageMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware",
"whitenoise.middleware.WhiteNoiseMiddleware",
]
CORS_ALLOW_CREDENTIALS = True
CORS_ALLOW_ALL_ORIGINS = True
CORS_ORIGIN_ALLOW_ALL = True
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.TokenAuthentication',
# 'rest_framework.authentication.SessionAuthentication'
)
}
AUTHENTICATION_BACKENDS = (
"django.contrib.auth.backends.ModelBackend",
"allauth.account.auth_backends.AuthenticationBackend",
)
here is the postman view. let me know if you need anything additional

You need to add a header X-CSRFToken while requesting a method that can potentially change a state on the server-side. For example, POST, PUT, PATCH, DELETE etc.
"headers" : { "X-CSRFToken":"token" }
The value of X-CSRFToken is set by the server when the user is authenticated. The server uses a header named set-cookie having a name csrf and value somevalue that can be used later in X-CSRF-Token header. Here is a link to complete documentation.
https://docs.djangoproject.com/en/3.1/ref/csrf/#acquiring-the-token-if-csrf-use-sessions-and-csrf-cookie-httponly-are-false

Related

DRF social authentication

Im implementing drf social oauth2 and while accessing the URL - localhost:8000/auth/login/facebook/ I get 'drf' is not a registered namespace, No ReverseMatch error and when I change my namespace to social, I get 'social' is not a registered namespace.
#URLPatterns
urlpatterns = [
path("admin/", admin.site.urls),
path('auth/', include('drf_social_oauth2.urls', namespace='social')),
path('api/', include("users.urls")),
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
#Installed Apps
INSTALLED_APPS = [
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
# rest
"rest_framework",
"corsheaders",
"rest_framework_simplejwt",
# Oauth
"oauth2_provider",
"social_django",
"drf_social_oauth2",
# apps
"accounts",
"inventory",
"cart",
"orders",
"users",
]

Use TokenAuthentication AND SocialAuthentication from AllAuth

I've created a new Django Rest API thanks to Django Rest Framework and I want to use two type of authentication : TokenAuthentication AND SocialAuthentication with two providers Facebook and Google.
Token authentication is success (with this syntax : Authorization: Token <token>. However, I can't get it to work with the SocialAuthentication. When I get the access_token from my POST in GoogleSocialLoginView, I can't use it to login in my others API call headers (I use an authenticated permissions for others CRUD calls). My syntax for social authentication is :
Authorization : Bearer <token>
So the users are registered successfully in database, but they can't authenticated us to the API after.
This is a part of my settings.py
AUTHENTICATION_BACKENDS = [
'django.contrib.auth.backends.ModelBackend',
'allauth.account.auth_backends.AuthenticationBackend',
]
# Application definition
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.sites',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'rest_framework',
'rest_framework_swagger',
'rest_framework.authtoken',
"dj_rest_auth",
'dj_rest_auth.registration',
'allauth',
'allauth.account',
'allauth.socialaccount',
'allauth.socialaccount.providers.facebook',
'allauth.socialaccount.providers.google',
# Local apps we created
'api',
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'corsheaders.middleware.CorsMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
ROOT_URLCONF = 'thetiptop.urls'
AUTH_USER_MODEL = 'api.Users'
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
REST_FRAMEWORK = {
'DEFAULT_SCHEMA_CLASS':'rest_framework.schemas.coreapi.AutoSchema',
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework.authentication.SessionAuthentication',
'rest_framework.authentication.TokenAuthentication',
],
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.IsAuthenticated',
),
}
REST_AUTH_SERIALIZERS = {
"LOGIN_SERIALIZER": "api.serializers.CustomLoginSerializer",
}
REST_USE_JWT = True
ACCOUNT_LOGOUT_ON_GET = True
OLD_PASSWORD_FIELD_ENABLED = True
SITE_ID = 2
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
CORS_ALLOW_ALL_ORIGINS = True
SOCIALACCOUNT_QUERY_EMAIL = True
ACCOUNT_AUTHENTICATION_METHOD = 'email'
ACCOUNT_EMAIL_REQUIRED = True
ACCOUNT_UNIQUE_EMAIL = True
ACCOUNT_USERNAME_REQUIRED = False
SOCIALACCOUNT_PROVIDERS = {
'google': {
'SCOPE': ['email'],
'AUTH_PARAMS': { 'access_type': 'online' }
},
'facebook': {
'METHOD': 'oauth2',
'SCOPE': ['email'],
'AUTH_PARAMS': {'auth_type': 'reauthenticate'},
'INIT_PARAMS': {'cookie': True},
'LOCALE_FUNC': lambda request: 'en_US',
'EXCHANGE_TOKEN': True,
'VERIFIED_EMAIL': False,
'VERSION': 'v13.0'
}
}
My url.py file list this URL :
path('auth/login/', obtain_auth_token, name='login'),
path('auth/', include('dj_rest_auth.urls')),
path('auth/registration/', include('dj_rest_auth.registration.urls')),
path('auth/facebook/', FacebookLogin.as_view(), name='fb_login'),
path('auth/google/', GoogleLogin.as_view(), name='google_login'),
path('accounts/', include('allauth.urls')),
And finally, the GoogleLogin and FacebookLogin are the same as :
from allauth.socialaccount.providers.facebook.views import FacebookOAuth2Adapter
from allauth.socialaccount.providers.google.views import GoogleOAuth2Adapter
from allauth.socialaccount.providers.oauth2.client import OAuth2Client
class FacebookLogin(SocialLoginView):
adapter_class = FacebookOAuth2Adapter
class GoogleLogin(SocialLoginView):
adapter_class = GoogleOAuth2Adapter
client_class = OAuth2Client
Finally, I use this type of authentication for others CRUD calls :
from rest_framework import permissions
permission_classes = [permissions.IsAuthenticated]
Do you know a solution to use a token authentication and a allauth social authentication in the same User class ?
PS: I'm consuming the API from an Angular app.
It's important to keep in mind that there is a difference between the default authentication system in Django and the authentication system for DRF. In your DRF configuration, you have only specified two classes: rest_framework.authentication.SessionAuthentication and rest_framework.authentication.TokenAuthentication. The SessionAuthentication is the only one that is compatible with the default Django session authentication backend (but that may not be what you want anyhow).
As covered in the DRF API guide if you are using session-based authentication, you must include a CSRF token for authenticated requests:
If you're using an AJAX-style API with SessionAuthentication, you'll need to make sure you include a valid CSRF token for any "unsafe" HTTP method calls, such as PUT, PATCH, POST or DELETE requests. [...]
CSRF validation in REST framework works slightly differently from 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.
So, if you are using a user's session (assuming you've authenticated the user's session previously) you will need to pass CSRF tokens when calling the DRF API in addition to the session cookie.
You may want to consider using an authentication method that works directly with DRF such as one recommended in the DRF docs like drf-social-oauth2. Then add it to your DRF authentication classes directly.

Django REST Framework BasicAuthentication is not applied as default authentication class

I have a Django REST Framework project and I added
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework.authentication.BasicAuthentication',
]
}
to settings.py and I expect BasicAuthentication is applied to all pages as default but still it does not require any authentication to display the content. that is weird. Do I have to do something I did not do?
urls.py:
urlpatterns = [
path('admin/', admin.site.urls),
path('student/', include('blogapp.urls')),
path('api/', include('api.urls')),
path('api-auth/', include('rest_framework.urls')),
]
setting.py:
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'rest_framework',
'blogapp',
]
blog_app/urls:
urlpatterns = [
path('', StudentView.as_view()),
]
views.py:
class StudentView(generics.ListCreateAPIView):
queryset = Student.objects.all()
serializer_class = StudentSerializer
UPDATE 1:
Also per-view authentications not work!
UPDATE 2:
This is my project source code.
Authentication is not the same as permission. You'll also need to add a default permission class if you require all users to be authenticated (using one of the authentication methods you wish to use):
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework.authentication.BasicAuthentication',
],
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.IsAuthenticated',
],
}
Also, make sure you're testing the right view (/api/list/). Your project (as you linked) has two StudentViews, one of which is a DRF view, the other (/student/) isn't. The latter will not be aware of DRF configuration.

Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://127.0.0.1:8000/api/task-list/

I'm working on a vuejs and django rest single page application.
the problem is so clear, that I'm getting the Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource error.
My vuex code be like:
mutations:{
addTask: function(){
axios({
url: 'http://127.0.0.1:8000/api/task-list/',
method: 'POST',
headers: {
'X-CSRFToken': getCookie('csrftoken')
}
}).then(response => {
console.log(response.data);
}).catch(err => {
console.log(err.response);
})
}
},
and my django rest urls.py, in my django application urls:
urlpatterns = [
path('task-list/', TaskList.as_view()),
path('task-create/', TaskCreate.as_view())
]
and my main app urls:
urlpatterns = [
path('admin/', admin.site.urls),
path('api/', include('task.urls')),
path('api/', include('authentication.urls')),
path('home/', returnHome)
]
my views.py code:
class TaskList(APIView):
def get(self, request):
tasks = Task.objects.all()
serialized = TaskSerializer(tasks, many=True)
return Response(serialized.data, status=200)
my vue app is running on http://localhost:8080, so I installed django cors headers, configured it like the below code:
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
# rest api
'rest_framework',
'rest_framework.authtoken',
'corsheaders',
# my apps
'authentication',
'task'
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'corsheaders.middleware.CorsMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
CORS_ALLOWED_ORIGINS = [
'http://localhost:8080',
'https://localhost:8080'
]
Although, I'm still getting the Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://127.0.0.1:8000/api/task-list/. (Reason: CORS request did not succeed) Error!
I even tried CORS_ALLOW_ALL_ORIGINS = True , but there are still no changes...
Is there something I'm doing wrong here? I searched a lot about this, but everywhere the solution was about to write the correct configuration for corsheaders.
You problem is the placement of the Django middleware, especially relatively to CommonMiddleware. It should be listed higher than it.
From django-cors-headers README:
CorsMiddleware should be placed as high as possible, especially before any middleware that can generate responses such as Django's CommonMiddleware or Whitenoise's WhiteNoiseMiddleware. If it is not before, it will not be able to add the CORS headers to these responses.
Also if you are using CORS_REPLACE_HTTPS_REFERER it should be placed before Django's CsrfViewMiddleware (see more below).

Is my request a cross origin request?(Django rest api on heroku with CORS not blocking my request)

I have hosted a django rest api on heroku and used the django-cors-headers in it. I have added a few URLs to the CORS whitelist but find that the app is accepting requests from any origin(Ex:- my local PC). And my query is, Why is heroku not blocking my http request even though it is not whitelisted.
Note:- I dont have any front end application running
Below is a snapshot of my settings.py
INSTALLED_APPS = (
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'corsheaders',
'rest_framework',
'rest_framework.authtoken',
)
MIDDLEWARE_CLASSES = (
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.security.SecurityMiddleware',
'django.middleware.common.BrokenLinkEmailsMiddleware',
'corsheaders.middleware.CorsMiddleware',
'django.middleware.common.CommonMiddleware',
)
# REST FRAMEWORK
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
# 'rest_framework.authentication.SessionAuthentication',
'rest_framework.authentication.TokenAuthentication',
],
# Use hyperlinked styles by default.
# Only used if the `serializer_class` attribute is not set on a view.
'DEFAULT_MODEL_SERIALIZER_CLASS':
'rest_framework.serializers.HyperlinkedModelSerializer',
# Use Django's standard `django.contrib.auth` permissions,
# or allow read-only access for unauthenticated users.
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.AllowAny',
],
# Make the default renderer class JSON when in production to prevent users from using the browsable API
'DEFAULT_RENDERER_CLASSES': [
'rest_framework.renderers.JSONRenderer',
]
}
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR)],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
DATABASES = {'default': dj_database_url.config(default=os.environ["HEROKU_POSTGRESQL_RED_URL"])}
# Internationalization
# https://docs.djangoproject.com/en/1.8/topics/i18n/
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'UTC'
USE_I18N = True
USE_L10N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/1.8/howto/static-files/
STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles')
STATICFILES_STORAGE = 'whitenoise.django.GzipManifestStaticFilesStorage'
CORS_ORIGIN_ALLOW_ALL = False
CORS_ALLOW_CREDENTIALS = True
CORS_ORIGIN_WHITELIST = (
#'localhost:9000',
#'localhost:5000',
#'127.0.0.1:9000',
#'127.0.0.1:5000',
)
CORS_ALLOW_METHODS = (
'GET',
'POST',
'PUT',
'PATCH',
'DELETE',
'OPTIONS'
)
CORS_ALLOW_HEADERS = (
'Access-Control-Allow-Origin',
'x-requested-with',
'content-type',
'accept',
'origin',
'authorization',
'x-csrftoken'
)
I was expecting it to work in the way it would when I use ALLOWED_HOSTS in the settings.py
ALLOWED_HOSTS = ['whitelist url']
Please let me know if I missed some setting here.
I think CORS_ALLOW_HEADERS should be like
CORS_ALLOW_HEADERS = (
'x-requested-with',
'content-type',
'accept',
'origin',
'authorization',
'x-csrftoken'
)
You should also add cache-control if you are using any.
Update
What would happen if you hosted the app on external servers and trying to access it locally?
Answer: When your local machine access the internet, it preserves an assigned available IP from ISP. Which means that the external server when got hit from your machine, It will not see it as 127.0.0.1 but it will see it as what appears here https://www.whatismyip.com/.
Solution: To allow cors origin from your local machine, do one of the following,
Add what appears on https://www.whatismyip.com/ to CORS_ORIGIN_WHITELIST. But this means that you will have to edit CORS_ORIGIN_WHITELIST every time you restart your local router. This is beacuse with restarting the local router, ISP will assign you another IP.
For testing purposes only, add '*' to CORS_ORIGIN_WHITELIST.
Try to have another heroku instance to make the requests.