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
Related
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.
I am using Django session auth with csrf middleware. And using angular for frontend. Front end makes a login request and my backend login logs in the user & csrf token is set in cookie, and passed in the further requests.
I am able to login and can see the cookie being passed on next requests, but I am getting 403 with Authentication credentials were not provided.
Here are my setttings.py && views.py.
MIDDLEWARE = [
'reversion.middleware.RevisionMiddleware',
'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',
]
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.IsAuthenticated',
),
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.SessionAuthentication',
'rest_framework.authentication.BasicAuthentication',
),
}
AUTHENTICATION_BACKENDS = (
'django.contrib.auth.backends.ModelBackend',
)
Here is my view.py
class LoginView(APIView):
serializer_class = CustomUserSerialzer
def post(self, request, *args, **kwargs):
data = request.data
email = data.get('email', None)
password = data.get('password', None)
try:
user = authenticate(request, username=email, password=password)
if user is not None:
login(request, user)
return Response(self.serializer_class(user).data)
raise Exception('Account has no access')
except Exception as e:
return Response({
'status': 'Unauthorized',
'message': str(e)
}, status=status.HTTP_401_UNAUTHORIZED)
I cannot think of any reason why it would fail on requests after successful login though csrf token passed. I am sure I am missing something in my settings. Can someone suggest what my mistake is?
Figured. I didn't notice I commented out 'django.contrib.auth.middleware.AuthenticationMiddleware'.
I have a question about using vue axios frontend and Django backend in cors-domain environment.
My cookie can use set-Cookie sessionid and csrftoken but frontend can't get these parameters to save to my document.
If I use my chrome explorer, the application cookie is empty but I can find the cookie in response cookie header.
But I won't use this csrftoken to backend verify in other POST AJAX, how can I resolve it to verify?
I try a lot of settings, but not work in using #csrf_protect function, though just remove it and use #csrf_exempt can work.
How can I keep csrftoken work in this situation?
Thanks.
The followings are what I use settings.
axios settings
axios.defaults.headers.get['Accept'] = 'application/json'
axios.defaults.headers.post['Content-Type'] = 'application/json'
axios.defaults.withCredentials = true
axios.defaults.xsrfCookieName = 'csrftoken'
axios.defaults.xsrfHeaderName = 'X-CSRFToken'
Django settings
MIDDLEWARE_CLASSES = (
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'corsheaders.middleware.CorsMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'django.middleware.security.SecurityMiddleware',
'django.middleware.gzip.GZipMiddleware',
)
CORS_ALLOW_HEADERS = (
'accept',
'accept-encoding',
'authorization',
'content-type',
'dnt',
'origin',
'user-agent',
'x-csrftoken',
'x-CSRFToken',
'X-CSRFToken',
'csrftoken',
'x-requested-with',
)
CORS_ALLOW_METHODS = (
'DELETE',
'GET',
'OPTIONS',
'PATCH',
'POST',
'PUT',
)
CORS_ORIGIN_ALLOW_ALL = True
CORS_ALLOW_CREDENTIALS = True
I have a ListView and it has permission set to IsAuthenticated, when I hit the URL in an incognito window, I'm able to view the data without having the user logged in.
Here is my serializer
class BlogListSerializer(ModelSerializer):
url = HyperlinkedIdentityField(
view_name="blog_api:post_detail",
lookup_field="slug"
)
class Meta:
model = Blog
fields = [
'url',
'title',
'category',
'date',
'publish',
'draft'
]
Below is my view
from rest_framework.permissions import IsAuthenticated
class BlogListAPIView(ListAPIView):
queryset = Blog.objects.filter(publish=True, draft=False)
serializer_class = BlogListSerializer
permission_classes = [IsAuthenticated]
Settings files
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.IsAuthenticated',
),
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.BasicAuthentication',
'rest_framework.authentication.SessionAuthentication',
)
}
Middleware settings
MIDDLEWARE_CLASSES = [
'django.middleware.security.SecurityMiddleware',
'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.clickjacking.XFrameOptionsMiddleware',
]
So what's happening is, when I try to access the user by calling get_object on BlogListAPIView, it throws an error is not JSON serializable. For some reason, middleware is taking AnonymousUser as a User. If there is AnonymousUser logged in it should fail IsAuthenticated permission. This is what basically should happen
Why AnonymousUser is getting accessed and IsAuthenticated() failing?
There is certainly some other problems that is not listed in your question. I created a fresh project with snippets you provided and will get http 401 when I hit the URL without logging in. I provided codes in Github:
get https://github.com/Rmaan/pastebin/tree/so-47596482
runserver and browse to http://localhost:8000/blog
The issue was gone by upgrading from Django 1.9 to Django 1.10 and using DRF 3.3.7.
I have an API written in Django, with authentication with cookie and sessionid.
Now I want to add a end-point public, with any kind of authentication, but I can't access the end-point.
What I've done:
URL:
url(r'^api/public_end_point/?$', csrf_exempt(views.publicEndPoint.as_view()), name='public_end_point')
Views:
class publicEndPoint(APIView):
#api_view(['POST'])
#permission_classes([permissions.AllowAny,])
def post(self, request, *args, **kwargs):
return Response({"success": True, "content": "Hello World!"})
Some Ideas about what am I missing here ?
Thanks
EDIT
Settings:
MIDDLEWARE_CLASSES = [
'django.middleware.security.SecurityMiddleware',
'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.clickjacking.XFrameOptionsMiddleware',
# MY MIDDLEWARE #
'config.middleware.EnforceLoginMiddleware',
'config.middleware.AuditMiddleware',
'config.middleware.ExceptionMiddleware',
]
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.IsAuthenticated',
),
'DEFAULT_FILTER_BACKENDS': ('rest_framework.filters.DjangoFilterBackend',),
'PAGE_SIZE': 20
}
[15/Feb/2017 15:57:16] "POST /api/public_end_point HTTP/1.1" 302 0
[15/Feb/2017 15:57:16] "GET /sign/base.html?nu=/api/public_end_point HTTP/1.1" 200 2906
Just add PERMISSION_CLASSES = (,) to your view as class variable, you don't need to use a decorator.
Just for informational purposes - Django Rest Framework by default uses BasicAuthentication and SessionAuthentication such that you either need to be logged in or provide Basic authentication credentials to access the api.
If you need your own custom token authentication, you need to override AUTHENTICATION_CLASSES in your drf settings.
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.BasicAuthentication',
'rest_framework.authentication.SessionAuthentication',
# add your own authentication class
)
}