Reputation: 88
I am creating a Django REST project. I created a custom user model in which I used rest-knox for token authentication. For the login and register user endpoints I used custom views where I authenticate the user's by their knox token. For password_change endpoint I used django auth's views.
Here is the problem I am facing:
views.py
from django.shortcuts import render
from rest_framework import viewsets, generics
from rest_framework.authtoken.models import Token
from rest_framework.response import Response
from knox.auth import TokenAuthentication
from knox.models import AuthToken
from rest_framework.permissions import IsAuthenticated, AllowAny
from .models import UserProfile
from .serializers import UserSerializer, LoginSerializer, KnoxSerializer, RegisterSerializer
from django.contrib.auth import views, decorators
from django.contrib.auth.decorators import user_passes_test
class UserListAPI(generics.ListAPIView):
authentication_classes = (TokenAuthentication,)
permission_classes = (AllowAny,)
queryset = UserProfile.objects.all()
serializer_class = UserSerializer
class UserDetailAPI(generics.RetrieveAPIView):
authentication_classes = (TokenAuthentication,)
permission_classes = (AllowAny,)
queryset = UserProfile.objects.all()
serializer_class = UserSerializer
lookup_field = 'username'
class LoginAPI(generics.GenericAPIView):
serializer_class = LoginSerializer
def post(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
user = serializer.validated_data
token = AuthToken.objects.create(user)[1]
return Response({
"user": UserSerializer(user, context=self.get_serializer_context()).data,
"token": token
})
class RegisterAPI(generics.GenericAPIView):
serializer_class = RegisterSerializer
queryset = UserProfile.objects.all()
def post(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
user = serializer.save()
return Response({
"user": UserSerializer(user, context=self.get_serializer_context()).data,
"token": AuthToken.objects.create(user)[1]
})
settings.py
import os
from decouple import config
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/3.0/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = config('SECRET_KEY')
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
ALLOWED_HOSTS = []
# Application definition
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'rest_framework',
'knox',
'rest_framework.authtoken',
'accounts',
'rest_auth',
]
MIDDLEWARE = [
'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 = 'Book_Lib_Project.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [],
'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',
],
},
},
]
WSGI_APPLICATION = 'Book_Lib_Project.wsgi.application'
# Database
# https://docs.djangoproject.com/en/3.0/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
}
}
# Password validation
# https://docs.djangoproject.com/en/3.0/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
# Internationalization
# https://docs.djangoproject.com/en/3.0/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/3.0/howto/static-files/
STATIC_URL = '/static/'
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': ('knox.auth.TokenAuthentication',),
'DATETIME_FORMAT': ("%m/%d/%Y %H:%M:%S",),
'DEFAULT_PERMISSION_CLASSES':(
'rest_framework.permissions.AllowAny',),
}
AUTHENTICATION_BACKENDS = [
"django.contrib.auth.backends.ModelBackend",
# #"accounts.backends.EmailAuthenticationBackend",
]
PASSWORD_HASHERS = [
'django.contrib.auth.hashers.BCryptSHA256PasswordHasher',
'django.contrib.auth.hashers.PBKDF2PasswordHasher',
'django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher',
'django.contrib.auth.hashers.Argon2PasswordHasher',
]
from datetime import timedelta
from rest_framework.settings import api_settings
REST_KNOX = {
'SECURE_HASH_ALGORITHM': 'cryptography.hazmat.primitives.hashes.SHA512',
'AUTH_TOKEN_CHARACTER_LENGTH': 64,
'TOKEN_TTL': timedelta(hours=10),
'USER_SERIALIZER': 'knox.serializers.UserSerializer',
'TOKEN_LIMIT_PER_USER': None,
'AUTO_REFRESH': False,
'EXPIRY_DATETIME_FORMAT': api_settings.DATETIME_FORMAT,
}
urls.py
from django.urls import path, include
from .views import UserListAPI, UserDetailAPI, LoginAPI, RegisterAPI, PasswordChangeAPI
from knox import views as knox_views
urlpatterns = [
path('logout/', knox_views.LogoutView.as_view(), name = "knox_logout"),
path('users/', UserListAPI.as_view()),
path('users/<str:username>/', UserDetailAPI.as_view()),
path('login/', LoginAPI.as_view()),
path('register/', RegisterAPI.as_view()),
path('', include('django.contrib.auth.urls')),
]
Index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
import axios from 'axios';
axios.defaults.withCredentials = true
axios.defaults.baseURL = 'https://jsonplaceholder.typicode.com';
axios.defaults.headers.post['Content-Type'] = 'application/json';
axios.defaults.xsrfCookieName = 'csrftoken'
axios.defaults.xsrfHeaderName = 'X-CSRFToken'
axios.interceptors.request.use(request => {
console.log(request.headers);
// Edit request config
return request;
}, error => {
console.log(error);
return Promise.reject(error);
});
axios.interceptors.response.use(response => {
console.log(response);
// Edit response config
return response;
}, error => {
console.log(error);
return Promise.reject(error);
});
ReactDOM.render(<App />, document.getElementById('root'));
serviceWorker.unregister();
ChangePassword.js
import React, {Component} from 'react';
//import ReactDOM from 'react-dom';
import { withCookies } from 'react-cookie';
import './ChangePassword.css';
import Axios from 'axios'
class ChangePassword extends Component {
state = {
credentials: {
old_password: '',
new_password1: '',
new_password2: '',
},
token: this.props.cookies.get('usertoken'),
}
inputChanged = event => {
let cred = this.state.credentials;
cred[event.target.name] = event.target.value;
this.setState({credentials: cred});
}
pressedEnter = event => {
if (event.key === 'Enter') {
this.changePassword();
}
}
changePassword = event => {
Axios.post('http://127.0.0.1:8000/accounts/password_change/', this.state.credentials, {headers: {
'Authorization': `Token ${this.state.token}`
}})
.then(res => {
console.log(res);
window.location.href = "/";
})
.catch( error => console.log(error))
}
render() {
return (
<div className="Login">
<form onSubmit={this.changePassword}>
<label>Old Password</label>
<input type="text" name="old_password" value={this.state.credentials.email} onChange={this.inputChanged} onKeyPress={this.pressedEnter}/><br/>
<label>New Password</label>
<input type="password" name="new_password1" value={this.state.credentials.password} onChange={this.inputChanged} onKeyPress={this.pressedEnter}/><br/>
<label>Confirm New Password</label>
<input type="password" name="new_password2" value={this.state.credentials.password} onChange={this.inputChanged} onKeyPress={this.pressedEnter}/><br/>
<input type="submit" value="Change" data-test="submit" />
</form>
</div>
)
}
}
export default withCookies(ChangePassword);
Upvotes: 3
Views: 1237
Reputation: 88
I solved the problem, so here is the solution if anyone encounters with the same problem and looks here.
At first, I needed to use the domain as http://127.0.0.1:3000 instead of http://localhost:3000 because of the restrictions given in https://curl.haxx.se/rfc/cookie_spec.html (check the section named as DOMAIN). Also, making some change on setting.py file was required.
Added below information to my settings.py file
CORS_ORIGIN_WHITELIST = (
'http://127.0.0.1:3000',
)
CORS_ORIGIN_ALLOW_ALL = True
CORS_ALLOW_CREDENTIALS = True
CORS_EXPOSE_HEADERS = (
'Access-Control-Allow-Origin: http://127.0.0.1:3000',
)
Changed my axios configuration in index.js to
axios.defaults.withCredentials = true
axios.defaults.baseURL = 'http://127.0.0.1:8000/';
axios.defaults.xsrfCookieName = 'csrftoken'
axios.defaults.xsrfHeaderName = 'x-csrftoken'
A proper way to make a post for login page is like:
Axios.post('http://127.0.0.1:8000/accounts/login/', this.state.credentials).then(res => {
console.log(res);
this.props.cookies.set('usertoken', res.data.token);
// Used token based authentication. A token is returned from backend in JSON format.
}).catch( error => console.log(error))
Upvotes: 3