Use TokenAuthentication AND SocialAuthentication from AllAuth - django

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.

Related

Getting "CSRF token missing or incorrect" on obtain token request

We are using Django REST Framework and we are using user logins. From a web client we have a login screen and use obtain_auth_token from the REST Framework to obtain an api token. The web client uses XMLHttpRequest.
It starts out with working fine. The web client obtains a token using username+password and uses that token in the following API calls.
When I return the next day and open a new browser tab and try to log in I get a 403 Forbidden and the Django logs (and the body reply) says {"detail":"CSRF Failed: CSRF token missing or incorrect."}
I can see that the incoming request has a csrftoken cookie and a sessionid cookie. I can see the same cookies if I use the browser "Developer Tools". If I remove those two cookies, it works fine afterwards.
Also, if I launch a private browser window (= incognito), the web app works fine.
I am do not know why those cookies appear, when they appear exactly and why the REST framework do not like them.
I have two suspicions:
We also use the Django admin interface. Could it be that the login to the admin interface on the same domain will plant those cookies and somehow interfere with the REST Framework?
Something about time passes will make the problem appear? It seems to me that the problem does not appear until "the day after" if I clear the cookies. This might very well be other circumstances tricking me, like point 1 above.
Any suggestions on how to resolve this?
For reference, some snippes of our Django setting.py:
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',
'request_logging.middleware.LoggingMiddleware',
]
INSTALLED_APPS = [
'mybackend.apps.MybackendConfig',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'django_cleanup.apps.CleanupConfig',
'rest_framework',
'rest_framework.authtoken',
'adminsortable',
'corsheaders',
'django_filters',
'storages',
]
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.DjangoModelPermissions'
],
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.TokenAuthentication',
'rest_framework.authentication.SessionAuthentication',
),
'DEFAULT_FILTER_BACKENDS': (
'django_filters.rest_framework.DjangoFilterBackend',
),
}
From urls.py:
from rest_framework.authtoken import views as restviews
...
urlpatterns = [
path('admin/', admin.site.urls),
url(r'^api/token-auth/obtain_auth_token', restviews.obtain_auth_token),
url(r'^api/', include(router.urls)),
]
Seems like the SessionAuthentication is the culprit. My guess is that is was added to be able to play with the REST interface from a browser for testing purposes.
So if I remove SessionAuthentication it seems to work.
Now the config looks like this:
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.DjangoModelPermissions'
],
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.TokenAuthentication',
# 'rest_framework.authentication.SessionAuthentication',
),
'DEFAULT_FILTER_BACKENDS': (
'django_filters.rest_framework.DjangoFilterBackend',
),
}

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).

What is the purpose of model Token in the admin section?

I am fairly new to the rest api. I am trying to use dj-rest-auth package with simple-jwt for auth handling. Everything works fine in my project. The registration/login etc. But in my django admin site there is a model registered Token which is every time empty. What is the purpose of this model Token? How tokens are managed with dj-rest-auth and simple jwt package ?
settings.py
installed_apps= [
..
'rest_framework',
'rest_framework.authtoken',
'dj_rest_auth',
'django.contrib.sites',
'allauth',
'allauth.account',
'allauth.socialaccount',
'dj_rest_auth.registration',
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework_simplejwt.authentication.JWTAuthentication',
)
}
REST_USE_JWT = True
SIMPLE_JWT = {
'ACCESS_TOKEN_LIFETIME': timedelta(minutes=5),
'REFRESH_TOKEN_LIFETIME': timedelta(days=7),
}
ACCOUNT_EMAIL_REQUIRED = True
ACCOUNT_EMAIL_VERIFICATION = "mandatory"
ACCOUNT_EMAIL_CONFIRMATION_EXPIRE_DAYS = 1
ACCOUNT_AUTHENTICATION_METHOD = "username"
ACCOUNT_LOGIN_ATTEMPTS_LIMIT = None
urls.py
path('user/', include('dj_rest_auth.urls')),
path('user/register/', include('dj_rest_auth.registration.urls')),
path('confirm/email/', CustomVerifyEmailView.as_view(), name='account_email_verification_sent'),
You have Token model in admin because you added rest_framework.authtoken to your installed apps. This model is for basic token (stored in db) authentication: https://www.django-rest-framework.org/api-guide/authentication/#tokenauthentication
JWT (JSON Web Tokens) tokens are stateless and are not stored in db. If you want to read more about JWT I recommend: https://jwt.io/introduction

JWT Token Authentication with Django Rest Framework

I am having some issues with token authentication on my Django-Rest application with a react native frontend. I have always used Session Authentication, and this is my first time setting up a project with these requirements.
I pip installed -
djangorestframework_simplejwt
I know the tokens are being generated when I hit the endpoint api/token
I am able to retrieve them on the front end. My problem occurs when I try to hit a list route in the backend and the error I get back is as follows.
{
"detail": "Authentication credentials were not provided."
}
I thought this could be a cors issue, or an issue with my axios request, but I am fairly certain they are setup correctly. The other issue is the authentication and permissions classes in my viewset which is where my intuition is telling me this problem is coming from.
Relevant settings.py info --
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',
'social_django.middleware.SocialAuthExceptionMiddleware',
]
CORS_ORIGIN_ALLOW_ALL = True
CORS_ALLOW_METHODS = (
'DELETE',
'GET',
'OPTIONS',
'PATCH',
'POST',
'PUT',
)
CORS_ALLOW_HEADERS = (
'accept',
'accept-encoding',
'authorization',
'content-type',
'dnt',
'origin',
'user-agent',
'x-csrftoken',
'x-requested-with',
)
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.authentication.TokenAuthentication',
'rest_framework.permissions.IsAuthenticated',
),
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework_simplejwt.authentication.JWTAuthentication',
'rest_framework.authentication.SessionAuthentication',
)
}
Viewset/Serializer/Url
class WorkoutViewSet(ModelViewSet):
model = apps.get_model('backend', 'Workout')
queryset = model.objects.all()
serializer_class = serializers.WorkoutSerializer
authentication_classes = (authentication.TokenAuthentication,)
permission_classes = (permissions.IsAuthenticated,)
class WorkoutSerializer(serializers.ModelSerializer):
class Meta:
model = apps.get_model('backend', 'Workout')
fields = ('name', 'is_complete', 'allow_copy', 'workout_goal', 'user')
router.register(r'workouts', views.WorkoutViewSet, base_name='workouts')
Axios Request
export const workouts = (token) => {
return axios({
method: 'get',
url: 'http://localhost:8000/workouts',
headers: { 'authorization': `Bearer ${token}`}
})
}
Thanks for any help/direction.
in your viewset, you have specified authentication_classes where you set TokenAuthentication.
That means, for your WorkoutViewSet you specified to use only TokenAuthentication - which uses Token prefix in the header, hence you get 'Credentials not provided'
If you want to use the JWT token authentication - you should either set it explicitly here or remove it and let the default chosen classes handle the authentication

Django CSRF missing or Sessions middleware required

When trying to create a django(2) rest framework api, I keep getting this error.
Forbidden (CSRF token missing or incorrect.): /login/
After doing some research the problem might be with sessions authentication which I don't really need since I will be replying on token based auth. When I try to remove some of the session auth from my settings I end up getting this.
AssertionError: The Django authentication middleware requires session middleware to be installed. Edit your MIDDLEWARE setting to insert 'django.contrib.sessions.middleware.SessionMiddleware' before 'django.contrib.auth.middleware.AuthenticationMiddleware'.
Settings.py snippet
INSTALLED_APPS = [
# API (v1)
'apiV1.v1.accounts.apps.AccountsConfig',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
# Requirements
'corsheaders',
'rest_framework',
'rest_framework.authtoken',
]
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',
]
ROOT_URLCONF = 'apiV1.urls'
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.IsAuthenticatedOrReadOnly',
),
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.BasicAuthentication',
'rest_framework.authentication.TokenAuthentication',
# 'rest_framework.authentication.SessionAuthentication',
),
}
Views.py
from django.contrib.auth import authenticate
from django.shortcuts import get_object_or_404
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from apiV1.v1.accounts.models.user import User
from apiV1.v1.accounts.serializers.user import UserSerializerLogin
# login
class LoginView(APIView):
authentication_classes = ()
permission_classes = ()
#staticmethod
def post(request):
"""
Get user data and API token
"""
user = get_object_or_404(User, email=request.data.get('email'))
user = authenticate(username=user.email, password=request.data.get('password'))
if user:
serializer = UserSerializerLogin(user)
return Response(serializer.data)
return Response(status=status.HTTP_400_BAD_REQUEST)
If you don't need session for authentication you have to use token authentication.
Here is the example of view
from rest_framework.authtoken.models import Token
from rest_framework.authtoken.views import ObtainAuthToken
from rest_framework.response import Response
class Login(ObtainAuthToken):
def post(self, request, *args, **kwargs):
"""
---
serializer: AuthTokenSerializer
"""
serializer = self.serializer_class(data=request.data)
serializer.is_valid(raise_exception=True)
user = serializer.validated_data['user']
token, created = Token.objects.get_or_create(user=user)
return Response({
'pk': user.pk,
'first_name': user.first_name,
'last_name': user.last_name,
})
Or you can use JWT authentication.
Here are some helpful links for you.
http://www.django-rest-framework.org/api-guide/authentication/
http://getblimp.github.io/django-rest-framework-jwt/