I have both JWT and session authentication middleware being used.
What i found was that i was not able to login to admin. Later when i removed JWT middleware it worked.
My site will is serving as both api login and normal browser login. How to use it for both.
The only option left is the below condition for jwt.
if request.content_type == 'application/json':
How to resolve this
I am not using DRF to create api endpoints. That why i have to create custom middleware to verify JWT token
Django settings:
MIDDLEWARE = (
'debug_toolbar.middleware.DebugToolbarMiddleware',
'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',
'webarticles.middleware.jwtWebtoken_mod.BaseJSONWebTokenAuthentication_mod',
)
webarticles.middleware.jwtWebtoken_mod.BaseJSONWebTokenAuthentication_mod
from rest_framework_jwt.authentication import JSONWebTokenAuthentication
from rest_framework import exceptions
import json
from django.http import HttpResponse
from rest_framework.settings import api_settings as api_settings2
from rest_framework_jwt.settings import api_settings
jwt_decode_handler = api_settings.JWT_DECODE_HANDLER
jwt_get_username_from_payload = api_settings.JWT_PAYLOAD_GET_USERNAME_HANDLER
class BaseJSONWebTokenAuthentication_mod(JSONWebTokenAuthentication):
"""
Token based authentication using the JSON Web Token standard.
"""
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
# Code to be executed for each request before
# the view (and later middleware) are called.
if request.content_type == 'application/json':
try:
user_auth_tuple = self.authenticate(request)
except exceptions.APIException as e:
self._not_authenticated(request)
hare = e.get_full_details()
#hare = {"e": str(e)}
# return HttpResponse(
# json.dumps(hare),
# content_type="application/json"
# )
return HttpResponse(
json.dumps(hare),
content_type="application/json",
status=e.status_code
)
if user_auth_tuple is not None:
request._authenticator = self
request.user, request.auth = user_auth_tuple
else:
self._not_authenticated(request)
response = self.get_response(request)
# Code to be executed for each request/response after
# the view is called.
return response
def _not_authenticated(self,request):
"""
Set authenticator, user & authtoken representing an unauthenticated request.
Defaults are None, AnonymousUser & None.
"""
request._authenticator = None
if api_settings2.UNAUTHENTICATED_USER:
request.user = api_settings2.UNAUTHENTICATED_USER()
else:
request.user = None
if api_settings2.UNAUTHENTICATED_TOKEN:
request.auth = api_settings2.UNAUTHENTICATED_TOKEN()
else:
request.auth = None
Presently I have put the below condition to manage
if request.content_type == 'application/json':
Use Postman to make requests and once you are successfully able to make requests. Generate code by pressing code button placed at right hand side.
I have made this node.js script from postman to login to particular website.
var request = require("request");
var options = {
method: 'POST',
url: 'http://abc.xyz.com/',
headers:
{
'postman-token': 'xxxx-xxxx-xx-xx-xxx',
'cache-control': 'no-cache',
authorization: 'Basic xxxxxxxxxxxxxxxxxxxxx',
'content-type': 'application/json'
},
body:
{
username: 'yourusername.com',
password: 'Your#password'
},
json: true
};
request(options, function (error, response, body)
{
if (error) throw new Error(error);
console.log(body);
});
Related
"test_put_method_success" is showing AssertionError: 404 != 200. How to solve it? ......................
class BasicTest(APITestCase):
def setUp(self):
self.client = Client()
self.user = User(username="admin", email="admin#gmail.com")
self.user.is_staff = True
self.user.set_password('admin')
self.user.save()
def test_put_method_success(self):
url = "http://127.0.0.1:8000/settings/modules/1/"
data = {
'modulename': "Module test update",
'activation_status': "Active"
}
self.assertTrue(self.client.login(username="admin", password="admin"))
response = self.client.put(url, data, format='json')
print(response.status_code)
self.assertEqual(response.status_code, status.HTTP_200_OK)
urls.py
from rest_framework import routers
router = routers.DefaultRouter()
router.register('modules', views.ModuleView)
urlpatterns = [
path('', include(router.urls)),
]
By default DRF PUT does not create an instance.
You need some extra steps as explained by the documentation.
I have this Django API view that I want to allow authorized and unauthorized users access it, I have set Django token-authentication as the default authentication class, however, whenever I try to access the view as unauthenticated user,I get error Unauthorized: which is weird coz am making a get request in the view
my code is here
#api_view(['GET'])
#permission_classes([permissions.IsAuthenticatedOrReadOnly])
def all_Search(request):
print(request.headers)
src = request.GET.get('q')
my settings for rest framework is
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework.authentication.TokenAuthentication',
'rest_framework.authentication.SessionAuthentication',
]
}
is there a way to work around this? will appreciate any help, thanks
I've tried to reproduce your error but I failed.
This is my configuration:
settings.py
INSTALLED_APPS = [
...
'rest_framework',
'rest_framework.authtoken'
]
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework.authentication.TokenAuthentication',
'rest_framework.authentication.SessionAuthentication',
]
}
urls.py
urlpatterns = [
path('search/', api.all_search, name="search")
]
api.py
from rest_framework import permissions
from rest_framework.decorators import api_view, permission_classes
from rest_framework.response import Response
#api_view(['GET'])
#permission_classes([permissions.IsAuthenticatedOrReadOnly])
def all_Search(request):
print(request.headers)
src = request.GET.get('q')
return Response()
test.py
from rest_framework import status
from rest_framework.test import APILiveServerTestCase
from rest_framework.reverse import reverse
class TestTokenAuthorization(APILiveServerTestCase):
def test_can_search_without_token(self):
url = reverse('search', kwargs={})
response = self.client.get(url, {}, format='json')
self.assertEqual(response.status_code, status.HTTP_200_OK)
and this is the result of the test:
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
{'Cookie': '', 'Content-Type': 'application/octet-stream'}
Destroying test database for alias 'default'...
I'm using djangorestframework==3.10.3 and python3.7
As you can see, I didn't authenticate the request (no token is passed) and the headers were printed as expected from the permissions.
Maybe your issue is caused by something else in your code. Try to include more details in your question.
By the way, your all_Search function is missing the return Response()
Okey I just decided to try something and it seams to be working, at least for now. I somehow believed that DEFAULT_AUTHENTICATION_CLASSES was the issue in this case and in deed it was, so I had to just remove the
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework.authentication.TokenAuthentication',
'rest_framework.authentication.SessionAuthentication',
]
}
#opt to use
#authentication_classes = [TokenAuthentication, SessionAuthentication]
#in my views that requires authentications
in my settings, this was not all though, but now I could access the view either authorized or not: (having auth token or not). but this was not getting authenticated user by default
so I did this
make a view to get a user based on a given token
from django.contrib.auth.models import AnonymousUser
from rest_framework.authtoken.models import Token
def get_user(token):
try:
token = Token.objects.select_related('user').get(key=token)
return token.user
except:
return AnonymousUser
and get user in my view if token exists in the headers
#api_view(['GET'])
#permission_classes([permissions.IsAuthenticatedOrReadOnly])
def all_Search(request):
auth = request.headers.get('Authorization').split(' ')[1]
key = request.headers.get('Authorization').split(' ')[0]
if key == 'Token' and auth != 'null': #used null coz my frontend sends null if key is not available
user = get_user(auth)
print(user)
I'm trying to set a Authentication middleware for django channels. I want this middleware to be active only for websocket requests.
Seems like that in this case i don't get a full middleware functionality. For example i can't get response = self.get_response(scope) working:
'TokenAuthMiddleware' object has no attribute 'get_response'
Everything is allright with this middleware now (it is activated only for websocket requests and not registered in settings.py), except that i need a means to modify a response status codes (block anonymous users and set the error code for ExpiredSignatureError). Any help appreciated. I use Django 2.0.6 and channels 2.1.1. jwt authentication by djangorestframework-jwt
middleware:
import jwt, re
import traceback
import logging
from channels.auth import AuthMiddlewareStack
from django.contrib.auth.models import AnonymousUser
from django.conf import LazySettings
from jwt import InvalidSignatureError, ExpiredSignatureError, DecodeError
from project.models import MyUser
settings = LazySettings()
logger = logging.getLogger(__name__)
class TokenAuthMiddleware:
"""
Token authorization middleware for Django Channels 2
"""
def __init__(self, inner):
self.inner = inner
def __call__(self, scope):
headers = dict(scope['headers'])
auth_header = None
if b'authorization' in headers:
auth_header = headers[b'authorization'].decode()
else:
try:
auth_header = _str_to_dict(headers[b'cookie'].decode())['X-Authorization']
except:
pass
logger.info(auth_header)
if auth_header:
try:
user_jwt = jwt.decode(
auth_header,
settings.SECRET_KEY,
)
scope['user'] = MyUser.objects.get(
id=user_jwt['user_id']
)
except (InvalidSignatureError, KeyError, ExpiredSignatureError, DecodeError):
traceback.print_exc()
pass
except Exception as e: # NoQA
logger.error(scope)
traceback.print_exc()
return self.inner(scope)
TokenAuthMiddlewareStack = lambda inner: TokenAuthMiddleware(AuthMiddlewareStack(inner))
def _str_to_dict(str):
return {k: v.strip('"') for k, v in re.findall(r'(\S+)=(".*?"|\S+)', str)}
routing.py
application = ProtocolTypeRouter({
# (http->django views is added by default)
'websocket': TokenAuthMiddlewareStack(
URLRouter(
cmonitorserv.routing.websocket_urlpatterns
)
),
})
Wasn't able to find a solution using middleware.
For now solved by handling auth permissions in consumers.py
def _is_authenticated(self):
if hasattr(self.scope, 'auth_error'):
return False
if not self.scope['user'] or self.scope['user'] is AnonymousUser:
return False
return True
Another important thing which doesn't seem to be documented anywhere - to reject a connection with the custom error code, we need to accept it first.
class WebConsumer(WebsocketConsumer):
def connect(self):
self.accept()
if self._is_authenticated():
....
else:
logger.error("ws client auth error")
self.close(code=4003)
I want to have 2 sessions, one for my application (myapp.com) and one for the admin (myapp.com/admin). With this, I can have access to both in different tabs of my web client without logging in every time I want to use one of them. It is very irritating.
I have created a new session middleware to control that.
import time
from importlib import import_module
from django.conf import settings
from django.contrib.sessions.backends.base import UpdateError
from django.core.exceptions import SuspiciousOperation
from django.utils.cache import patch_vary_headers
from django.utils.deprecation import MiddlewareMixin
from django.utils.http import http_date
class AdminCookieSessionMiddleware(MiddlewareMixin):
def __init__(self, get_response=None):
self.get_response = get_response
engine = import_module(settings.SESSION_ENGINE)
self.SessionStore = engine.SessionStore
def cookie_name(self, request):
parts = request.path.split('/')
if len(parts) > 1 and parts[1].startswith('admin'):
return settings.ADMIN_SESSION_COOKIE_NAME
return settings.SESSION_COOKIE_NAME
def cookie_path(self, request):
parts = request.path.split('/')
if len(parts) > 1 and parts[1].startswith('admin'):
return settings.ADMIN_SESSION_COOKIE_PATH
return settings.SESSION_COOKIE_PATH
def process_request(self, request):
session_key = request.COOKIES.get(self.cookie_name(request))
request.session = self.SessionStore(session_key)
def process_response(self, request, response):
"""
If request.session was modified, or if the configuration is to save the
session every time, save the changes and set a session cookie or delete
the session cookie if the session has been emptied.
"""
try:
accessed = request.session.accessed
modified = request.session.modified
empty = request.session.is_empty()
except AttributeError:
pass
else:
# First check if we need to delete this cookie.
# The session should be deleted only if the session is entirely empty
if settings.SESSION_COOKIE_NAME in request.COOKIES and empty:
response.delete_cookie(
settings.SESSION_COOKIE_NAME,
path=settings.SESSION_COOKIE_PATH,
domain=settings.SESSION_COOKIE_DOMAIN,
)
else:
if accessed:
patch_vary_headers(response, ('Cookie',))
if (modified or settings.SESSION_SAVE_EVERY_REQUEST) and not empty:
if request.session.get_expire_at_browser_close():
max_age = None
expires = None
else:
max_age = request.session.get_expiry_age()
expires_time = time.time() + max_age
expires = http_date(expires_time)
# Save the session data and refresh the client cookie.
# Skip session save for 500 responses, refs #3881.
if response.status_code != 500:
try:
request.session.save()
except UpdateError:
raise SuspiciousOperation(
"The request's session was deleted before the "
"request completed. The user may have logged "
"out in a concurrent request, for example."
)
response.set_cookie(
self.cookie_name(request),
request.session.session_key, max_age=max_age,
expires=expires, domain=settings.SESSION_COOKIE_DOMAIN,
path=settings.SESSION_COOKIE_PATH,
secure=settings.SESSION_COOKIE_SECURE or None,
httponly=settings.SESSION_COOKIE_HTTPONLY or None,
)
return response
In my settings file:
SESSION_COOKIE_NAME='usersessionid'
ADMIN_SESSION_COOKIE_NAME='adminsessionid'
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',
'core.utils.middleware.AdminCookieSessionMiddleware'
]
However, I have still the problem that if logging in one of the apps, i got automatically logged out of the other.
I was tracing the sessionkey and sometimes it is the same for both coockies. is that ok? If not, what should the problem be?
OK, I could fix it, therefore I post the answer so I can help somebody trying to do that as well. I copy the code from the django SessionMiddleware and added a cookie_name function. Inside I created the logic that should apply for my needs.
Remember to replace settings.SESSION_COOKIE_NAME for cookie_name.
import time
from importlib import import_module
from django.conf import settings
from django.contrib.sessions.backends.base import UpdateError
from django.core.exceptions import SuspiciousOperation
from django.utils.cache import patch_vary_headers
from django.utils.deprecation import MiddlewareMixin
from django.utils.http import http_date
class MySessionMiddleware(MiddlewareMixin):
def __init__(self, get_response=None):
self.get_response = get_response
engine = import_module(settings.SESSION_ENGINE)
self.SessionStore = engine.SessionStore
def cookie_name(self, request):
parts = request.path.split('/')
if settings.DEBUG:
if len(parts)>1 and parts[1].startswith('admin'):
return settings.ADMIN_SESSION_COOKIE_NAME
else:
if len(parts)>2 and parts[2].startswith('admin'):
return settings.ADMIN_SESSION_COOKIE_NAME
return settings.SESSION_COOKIE_NAME
def process_request(self, request):
session_key = request.COOKIES.get(self.cookie_name(request))
request.session = self.SessionStore(session_key)
def process_response(self, request, response):
"""
If request.session was modified, or if the configuration is to save the
session every time, save the changes and set a session cookie or delete
the session cookie if the session has been emptied.
"""
try:
accessed = request.session.accessed
modified = request.session.modified
empty = request.session.is_empty()
except AttributeError:
pass
else:
# First check if we need to delete this cookie.
# The session should be deleted only if the session is entirely empty
cookie_name = self.cookie_name(request)
if cookie_name in request.COOKIES and empty:
response.delete_cookie(
cookie_name,
path=settings.SESSION_COOKIE_PATH,
domain=settings.SESSION_COOKIE_DOMAIN,
)
else:
if accessed:
patch_vary_headers(response, ('Cookie',))
if (modified or settings.SESSION_SAVE_EVERY_REQUEST) and not empty:
if request.session.get_expire_at_browser_close():
max_age = None
expires = None
else:
max_age = request.session.get_expiry_age()
expires_time = time.time() + max_age
expires = http_date(expires_time)
# Save the session data and refresh the client cookie.
# Skip session save for 500 responses, refs #3881.
if response.status_code != 500:
try:
request.session.save()
except UpdateError:
raise SuspiciousOperation(
"The request's session was deleted before the "
"request completed. The user may have logged "
"out in a concurrent request, for example."
)
response.set_cookie(
cookie_name,
request.session.session_key, max_age=max_age,
expires=expires, domain=settings.SESSION_COOKIE_DOMAIN,
path=settings.SESSION_COOKIE_PATH,
secure=settings.SESSION_COOKIE_SECURE or None,
httponly=settings.SESSION_COOKIE_HTTPONLY or None,
)
return response
And this is very important: in settings, in the MIDDELWARE list, you must add it but also remove the SessionMiddleware per default. Insert it in the same position where the normal SessionMiddleware was. The idea is to put it before 'django.contrib.auth.middleware.AuthenticationMiddleware', otherwise, you will get an error.
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
#'django.contrib.sessions.middleware.SessionMiddleware',
'core.utils.middleware.MySessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
I'm writing some tests for my rest_framework API, and I'm using token authentication to secure it. I've decided to use DRF's APIClient class to simulate calls from a user's browser.
I can grab tokens just fine from the API by hitting the authentication endpoint, but when I try to use those tokens to authenticate any further requests to other endpoints, I get back a 401 Unauthorized error with the message, "Invalid token".
Curiously, I can copy-paste the exact same token and make a successful, manual GET request to that exact same endpoint via something like HTTPIE...
Here's my tests.py:
import json
from rest_framework import status
from rest_framework.test import APIClient
from rest_framework.test import APITestCase
class TestUser(object):
"""
A basic user class to simplify requests to the API
Tokens can be generated by authing as a user to /v1/auth/
"""
def __init__(self, token):
self.client = APIClient()
self.token = token
self.client.credentials(HTTP_AUTHORIZATION='Token ' + token)
def get(self, url):
print("Token: {0}".format(self.token))
res = self.client.get(url)
print('GET {0}: {1}'.format(url, res.data))
return res
def post(self, url, data):
res = self.client.post(url, data, format='json')
print('POST {0}: {1}'.format(url, res.data))
return res
def patch(self, url, data):
res = self.client.patch(url, data, json=data)
print('PATCH {0}: {1}'.format(url, res.data))
return res
def delete(self, url):
res = self.client.delete(url)
print('DELETE {0}: {1}'.format(url, res.data))
return res
# Grab new tokens every time we run our tests
auth_client = APIClient()
SUPERUSER = TestUser(auth_client.post('/v1/auth/', {'username': 'TestUser',
'password': 'password'}).data['token'])
ADMIN = TestUser(auth_client.post('/v1/auth/', {'username': 'TestUser4',
'password': 'password'}).data['token'])
MANAGER = TestUser(auth_client.post('/v1/auth/', {'username': 'TestUser2',
'password': 'password'}).data['token'])
EMPLOYEE = TestUser(auth_client.post('/v1/auth/', {'username': 'TestUser3',
'password': 'password'}).data['token'])
class AdminSiteCompanies(APITestCase):
def test_list_crud_permissions(self):
# GET
url = "/v1/admin_site/companies/"
self.assertEqual(SUPERUSER.get(url).status_code, status.HTTP_200_OK)
self.assertEqual(ADMIN.get(url).status_code, status.HTTP_200_OK)
self.assertEqual(MANAGER.get(url).status_code, status.HTTP_403_FORBIDDEN)
self.assertEqual(EMPLOYEE.get(url).status_code, status.HTTP_403_FORBIDDEN)
This is console output from the above test showing that a valid token is received from the API, just before it spits back a 401 when I try to use it in a test:
Creating test database for alias 'default'...
Token: d579dbe4980d8ac451a462fc78cf38f789decddf
GET /v1/admin_site/companies/: {'detail': 'Invalid token.'}
Destroying test database for alias 'default'...
Here's console output from me making a successful manual GET request using HTTPIE and the above token:
D:\Projects\API-Server>http http://127.0.0.1:8000/v1/admin_site/companies/ "Authorization: Token d579dbe4980d8ac451a462fc78cf38f789decddf"
HTTP/1.0 200 OK
Allow: GET, POST, HEAD, OPTIONS
Content-Type: application/json
Date: Fri, 01 May 2015 05:43:59 GMT
Server: WSGIServer/0.2 CPython/3.4.3
Vary: Accept
X-Frame-Options: SAMEORIGIN
[
{
"address": "1234 Fake Street",
"id": 1,
"name": "FedEx",
"shift_type": "OE"
},
{
"address": "Bolivia",
"id": 2,
"name": "UPS",
"shift_type": "PS"
}
]
Here's the relevant bits from my settings.py:
INSTALLED_APPS = (
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'rest_framework',
'rest_framework.authtoken',
'serverapp',
'rest_framework_swagger',
)
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.clickjacking.XFrameOptionsMiddleware',
'django.middleware.security.SecurityMiddleware',
'serverapp.middlewares.EmployeeMiddleware',
)
ROOT_URLCONF = 'shiftserver.urls'
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.TokenAuthentication',
),
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.IsAuthenticated',
),
'DEFAULT_FILTER_BACKENDS': (
'rest_framework.filters.DjangoFilterBackend',
)
}
# Database
# https://docs.djangoproject.com/en/1.8/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
}
}
This is the first time I've ever written tests for Django/rest_framework, so I've been diligently following DRF's documentation on testing and authenticating. No matter what I try, though, I still can't get past this "invalid token" issue.
A friend who's way more versed in DRF than me was left stumped when I asked him for help with this, so hopefully you guys can reveal what we're both missing.
I figured it out! POSTing to the API outside of a TestCase class hits the actual API server that I happened to have running while I was running my tests. I refactored AdminSiteCompanies(APITestCase) to set up test data, users, and authenticate those users all within the class's setUp(self):
class AdminSiteCompanies(APITestCase):
def setUp(self):
# Create test Objects here
...snip...
# Create test Users here
# SuperUser
create_user('TestUser', 'password', 'testuser#test.com', True, False, False, co1lo1.id)
...snip...
# Grab new tokens every time we run our tests
# APIClient allows us to emulate calls from a browser
auth_client = APIClient()
# Authenticate our users
self.SUPERUSER = TestUser(auth_client.post('/v1/auth/', {'username': 'TestUser', 'password': 'password'})
.data['token'])
...snip...
def test_list_crud_permissions(self):
# GET
url = "/v1/admin_site/companies/"
self.assertEqual(self.SUPERUSER.get(url).status_code, status.HTTP_200_OK)
# ^ Now passes test
...snip...