Customizing JWT response from django-rest-framework-simplejwt - json

I'm setting up Django to send a JWT Response as opposed to a view. I tried using django-rest-framework-simplejwt.
Provided in this framework, there is a function TokenObtainPairView.as_view() that returns a pair of jwt. I need to return the access token with another Json response as opposed to the two tokens provided.
Ideally I would like one JsonResponse that contains an access token that is the same as this one: TokenObtainPairView.as_view().
I tried creating my own view which is provided below.
UPDATE: Provided in Settings.py
SIMPLE_JWT = {
'ACCESS_TOKEN_LIFETIME': timedelta(days=1),
'REFRESH_TOKEN_LIFETIME': timedelta(days=1),
'ROTATE_REFRESH_TOKENS': False,
'BLACKLIST_AFTER_ROTATION': True,
'ALGORITHM': 'HS256',
'SIGNING_KEY': SECRET_KEY,
'VERIFYING_KEY': None,
'AUTH_HEADER_TYPES': ('Bearer',),
'USER_ID_FIELD': 'id',
'USER_ID_CLAIM': 'user_id',
'AUTH_TOKEN_CLASSES': ('rest_framework_simplejwt.tokens.AccessToken',),
'TOKEN_TYPE_CLAIM': 'token_type',
'SLIDING_TOKEN_REFRESH_EXP_CLAIM': 'refresh_exp',
'SLIDING_TOKEN_LIFETIME': timedelta(days=1),
'SLIDING_TOKEN_REFRESH_LIFETIME': timedelta(days=1),
}
Login URL Path
urlpatterns = [
path('auth/', views.LoginView.as_view()),
]
LoginView I created
class LoginView(APIView):
permission_classes = (AllowAny,)
def post(self, request, *args, **kwargs):
username = request.data['username']
password = request.data['password']
user = authenticate(username=username, password=password)
if user is not None:
payload = {
'user_id': user.id,
'exp': datetime.now(),
'token_type': 'access'
}
user = {
'user': username,
'email': user.email,
'time': datetime.now().time(),
'userType': 10
}
token = jwt.encode(payload, SECRET_KEY).decode('utf-8')
return JsonResponse({'success': 'true', 'token': token, 'user': user})
else:
return JsonResponse({'success': 'false', 'msg': 'The credentials provided are invalid.'})
Pattern provided by framework.
urlpatterns = [
...
path('token/', TokenObtainPairView.as_view()),
...
]
It returns this token
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiZXhwIjoxNTQ5NDk3NDQ2LCJqdGkiOiI3YmU4YzkzODE4MWI0MmJlYTFjNDUyNDhkNDZmMzUxYSIsInVzZXJfaWQiOiIwIn0.xvfdrWf26g4FZL2zx3nJPi7tjU6QxPyBjq-vh1fT0Xs
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ0b2tlbl90eXBlIjoicmVmcmVzaCIsImV4cCI6MTU0OTQ5NzQ0NiwianRpIjoiOTNhYzkxMjU5NmZkNDYzYjg2OGQ0ZTM2ZjZkMmJhODciLCJ1c2VyX2lkIjoiMCJ9.dOuyuFuMjkVIRI2_UcXT8_alCjlXNaiRJx8ehQDIBCg
If you go to https://jwt.io/ you will see what's returned

For example: to customize simpleJWT response by adding username and groups,
Override the validate method in TokenObtainPairSerializer
# project/views.py
from rest_framework_simplejwt.serializers import TokenObtainPairSerializer
from rest_framework_simplejwt.views import TokenObtainPairView
class MyTokenObtainPairSerializer(TokenObtainPairSerializer):
def validate(self, attrs):
data = super().validate(attrs)
refresh = self.get_token(self.user)
# Add extra responses here
data['username'] = self.user.username
data['groups'] = self.user.groups.values_list('name', flat=True)
return data
class MyTokenObtainPairView(TokenObtainPairView):
serializer_class = MyTokenObtainPairSerializer
replace the login view with customized view
# project/urls.py
from .views import MyTokenObtainPairView
urlpatterns = [
# path('token/', TokenObtainPairView.as_view(), name='token_obtain_pair'),
path('token/', MyTokenObtainPairView.as_view(), name='token_obtain_pair'),
]
🔚
References: SimpleJWT Readme and the source code as below:

A very clean approach from the django-rest-framework-simplejwt README as below
For example, I have users app where my custom User model resides. Follow serializers and views code example below.
users/serializers.py:
from rest_framework_simplejwt.serializers import TokenObtainPairSerializer
class MyTokenObtainPairSerializer(TokenObtainPairSerializer):
#classmethod
def get_token(cls, user):
token = super().get_token(user)
# Add custom claims
token['name'] = user.name
# Add more custom fields from your custom user model, If you have a
# custom user model.
# ...
return token
users/views.py:
from rest_framework_simplejwt.views import TokenObtainPairView
class MyTokenObtainPairView(TokenObtainPairView):
serializer_class = MyTokenObtainPairSerializer
After that, Register above View in your project's urls.py replacing original TokenObtainPairView as below.
from users.views import MyTokenObtainPairView
urlpatterns = [
..
# Simple JWT token urls
# path('api/token/', TokenObtainPairView.as_view(),
# name='token_obtain_pair'),
path('api/token/', CustomTokenObtainPairView.as_view(),
name='token_obtain_pair')
..
]
Now get access token and decode it at jwt.io

Custom Token response like this:
from rest_framework_simplejwt.tokens import RefreshToken
class LoginView(APIView):
permission_classes = (AllowAny,)
def post(self, request, *args, **kwargs):
# ...
# Get user token, include refresh and access token
token = RefreshToken.for_user(user)
# Serializer token if it is not correct JSON format ^^
return JsonResponse({'success': 'true', 'token': token, 'user': user})

I had the same issue this can be resolved by something like this if you want to handle the custom response in case of an invalid credential.
# JWT View
class LoginView(TokenObtainPairView):
'''
Returns access token and refresh token on login
'''
permission_classes = (AllowAny,)
serializer_class = MyTokenObtainPairSerializer
def post(self, request, *args, **kwargs):
try:
response = super().post(request, *args, **kwargs)
return response
except exceptions.AuthenticationFailed:
return Response({
'error': True,
'message': 'Invalid Username or Password',
}, status=status.HTTP_401_UNAUTHORIZED)

Related

Is it possible to send a request to django rest api to run a script?

I installed Django and Django Rest Api. I want to send some data to rest api. Rest api will take the data and run a script with this data and get a result. Then send this result back to me.
There won't be database usage.
Like this, request : http://testerapi.com:8000/search?q=title:xfaster564CertVal9body:A%22&fl=id
Response : {validation : true}
Is it possible?
Yes it is possible ! But i will try to respond with api function based view.
Let's suppose that our worker function to call when call the API (GET or POST) is in the utilities.py file, the models.py, serializers.py and views.py.
utilities.py
def my_worker(a, b=0, c=0):
# do something with a, b, c
return a + b + c > 10
models.py
from datetime import datetime
class User(object):
def __init__(self, email, name, created = None):
self.email = email
self.name = name
self.created = created or datetime.now()
serializers.py
I use simple Serializer but ModelSerializer is better i think
from rest_framework import serializers
class UserSerializer(serializers.Serializer):
# initialize fields
email = serializers.EmailField()
name = serializers.CharField(max_length = 200)
created = serializers.DateTimeField()
views.py
from django.http import JsonResponse
from django.views.decorators.csrf import csrf_exempt # Allow request without csrf_token set
from rest_framework.decorators import api_view
from .models import User
from .serializers import UserSerializer
# Import my_worker from .utilities
from .utilities import my_worker
#csrf_exempt
#api_view('GET') # Only get request is allowed
def user_worker(request, a, b, c):
"""
Do something with
"""
if request.method == 'GET':
# Do some stuff
users = User.objects.all()
serializer = UserSerializer(users, many=True)
# Call the utilities script here
result = my_worker(a, b, c)
if result: # a+b+c > 10
return JsonResponse({"validation": "true"}, safe=False)
else:
return JsonResponse({"validation": "false"}, safe=False)
Note that i dont use the UserSerializer but show it at example.
You can then execute a more complex function (here the my_worker).
Adapt it according to your needs.

How to make Django views require a user login?

I have a Django project with a login screen; when I enter my username and password I get directed to the home page. This works fine! But nothing is secure, I can just go to the view at /allstudyplans without logging in and see all the information there. My Question is how do I make it so it's not possible to go /allstudyplans without logging in first?
Form
User = get_user_model()
class UserLoginForm(forms.Form):
username = forms.CharField(label='', widget=forms.TextInput(
attrs={
'class': 'form-control',
'autofocus': '',
'placeholder': 'Användarnamn',
}
), )
password = forms.CharField(label='', widget=forms.PasswordInput(
attrs={
'class': 'form-control mt-1',
'placeholder': 'Lösenord',
}
), )
# https://docs.djangoproject.com/en/2.1/ref/forms/validation/#cleaning-and-validating-fields-that-depend-on-each-other
def clean(self):
cleaned_data = super().clean()
username = cleaned_data.get('username')
password = cleaned_data.get('password')
if username and password:
user = authenticate(username=username, password=password)
if not user:
raise forms.ValidationError(
'Oh! I can\'t find that user - create user first!')
elif not user.check_password(password):
raise forms.ValidationError(
'Oh! That password is incorrect - try again!')
elif not user.is_active:
raise forms.ValidationError(
'Oh! That user is not active in the database!')
Views
def home(request):
next = (request.GET.get('next'))
form = UserLoginForm(request.POST or None)
if form.is_valid():
username = form.cleaned_data.get('username')
password = form.cleaned_data.get('password')
user = authenticate(username=username, password=password)
login(request, user)
if next:
return redirect(next)
return redirect('/')
context = {
'form': form,
}
return render(request, "utility-login.html", context)
def Assignments(request):
return render(request, 'nav-side-team.html')
def DetailAssignment(request):
obj = Assignment.objects.all()
context = {
'object': obj
}
return render(request, 'nav-side-project.html', context)
def studyplans(request):
return render(request, 'allStudyplans.html')
def detailStudyplan(request):
return render(request, 'detailStudyplan.html')
Also a Home View in the normal project file (not the app)
#login_required
def homee(request):
return render(request, "allstudyplans.html", {})
Urls in the project:
urlpatterns = [
path('admin/', admin.site.urls),
path('account/', include('accounts.urls')),
path('', include('accounts.urls'))
]
Urls in the app:
urlpatterns = [
path('', views.studyplans),
path('login', views.home),
path('nav-side-team.html', views.Assignments),
path('nav-side-project.html', views.DetailAssignment),
path('allstudyplans.html', views.studyplans),
path('detailStudyplan.html', views.detailStudyplan),
]
Tried this:
#login_required
def Assignments(request):
if not request.user.is_authenticated:
return redirect('%s?next=%s' % ('utility-login.html', request.path))
return render(request, 'nav-side-team.html')
studyplans is missing the decorator login_required.
According to the docs, https://docs.djangoproject.com/en/2.2/topics/auth/default/#the-login-required-decorator, the easiest way should be
#login_required
def studyplans(request):
return render(request, 'allStudyplans.html')
Another way to do it – probably equivalent to the above – is with "mixins":
from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin
class MyView(LoginRequiredMixin, View):
...
Notice that "mixin" class names should occur first in the list, before View.
(Notice that I also show that there's a "permission-required mixin.")
I guess it just comes down to personal preference, and consistency with the existing source-code of the application.

voice call ends right away, nexmo hangs up within a second

I have created an application in the Nexmo dashboard with an event url and answer url. I run the following code I got from Nexmo´s GitHub:
client = nexmo.Client(key=api_key, secret=api_secret, application_id=application_key, private_key=private_key)
response = client.create_call({
'to': [{'type': 'phone', 'number': 'call_to_number'}],
'from': {'type': 'phone', 'number': 'call_from_number'},
'answer_url': ['http://my_event_url']
})
And the phonenumber is called, but nexmo hangs up right away (within a second without saying anything).
On my server I see Nexmo calls the answer url (with the ncco)
what I do when the answer url is called:
import nexmo
import json
from django.http import JsonResponse
#csrf_exempt
def answer(request):
ncco = [{
"action": "talk",
"voiceName": "Amy",
"text": "Thank you for calling Nexmo. Please leave your message after the tone."
}]
d = json.dumps(ncco)
j = is_json(d)
if j:
MyObject.objects.create(message="valid")
else:
MyObject.objects.create(message=user_ip + "json error")
return JsonResponse(d, status=200, safe=False)
def is_json(myjson):
try:
json_object = json.loads(myjson)
except ValueError:
return False
return True
This is what I do when my event_url is called:
#csrf_exempt
def event(request):
d = json.dumps(request.POST)
DataReceived.objects.create(answer=d)
data = {"ok": True}
return JsonResponse(data, status=200)
The event url is called 5 times by Nexmo but the dictionaries are always empty.
`I think you might be "double JSON dumping" your NCCO,
you create the ncco as a python dict, then turn that into a json string with d = json.dumps(ncco) and then you are calling JsonResponse on it, try passing the ncco object to JsonResponse
return JsonResponse(ncco, status=200, safe=False)

How to get the same information for all templates?

So in Django, i have a base template which has some contact details in it. but every view that i generate i have to have the line.
contact = Contact.objects.first()
Then i have to add that object to the dictionary that's loaded with the template.
What is the better way to deal with is? I find it hard to believe that i'm doing it in the correct way.
Examaple views.py
from django.shortcuts import render
from services.models import Service, ServicesDetail
from .models import Feature, CompanyDetail, TeamMember, TeamDetail, Banner
from contact.models import ContactDetail
import json
# Create your views here.
def home(request):
services = Service.objects
try:
overview = ServicesDetail.objects.first()
except ServicesDetail.DoesNotExist:
overview = ''
try:
company = CompanyDetail.objects.first()
except CompanyDetail.DoesNotExist:
company = ''
features = Feature.objects
contact_details = ContactDetail.objects.first()
banners = Banner.objects
return render(request, 'home.html', {'overview': overview,
'services': services,
'company': company,
'features': features,
'contact_detail': contact_details,
'banners': banners})
def company(request):
services = Service.objects
try:
company = CompanyDetail.objects.first()
except CompanyDetail.DoesNotExist:
company = ''
features = Feature.objects
contact_details = ContactDetail.objects.first()
return render(request, 'company.html', {'services': services,
'company': company,
'features': features,
'contact_detail': contact_details,})
def team(request):
services = Service.objects
members = TeamMember.objects
try:
teampage = TeamDetail.objects.first()
except TeamDetail.DoesNotExist:
teampage = ''
contact_details = ContactDetail.objects.first()
return render(request, 'team.html', {'services': services,
'members': members,
'teampage': teampage,
'contact_detail': contact_details,})
you can switch to class base template views and write your custom base class
class MyBaseTemplateView(TemplateView):
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['contact_details'] = ContactDetail.objects.first()
return context
class MyActualView(MyBaseTemplateView):
template_name = 'company.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
# set other specific context values for this view here
return context
and add these views to your urls.py like this:
urlpatterns = [
path('', MyActualView.as_view(), name='myactualview'),
]
You don't need to do it in each view, just write custom context processor:
def contact_details(request):
return {'contact_detail': contact_details = ContactDetail.objects.first()}
And add it to TEMPLATES setting:
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',
'path.to.processor.contact_details'
],
},
},
]

django postgresql json field schema validation

I have a django model with a JSONField (django.contrib.postgres.fields.JSONField)
Is there any way that I can validate model data against a json schema file?
(pre-save)
Something like my_field = JSONField(schema_file=my_schema_file)
I wrote a custom validator using jsonschema in order to do this.
project/validators.py
import django
from django.core.validators import BaseValidator
import jsonschema
class JSONSchemaValidator(BaseValidator):
def compare(self, value, schema):
try:
jsonschema.validate(value, schema)
except jsonschema.exceptions.ValidationError:
raise django.core.exceptions.ValidationError(
'%(value)s failed JSON schema check', params={'value': value}
)
project/app/models.py
from django.db import models
from project.validators import JSONSchemaValidator
MY_JSON_FIELD_SCHEMA = {
'schema': 'http://json-schema.org/draft-07/schema#',
'type': 'object',
'properties': {
'my_key': {
'type': 'string'
}
},
'required': ['my_key']
}
class MyModel(models.Model):
my_json_field = models.JSONField(
default=dict,
validators=[JSONSchemaValidator(limit_value=MY_JSON_FIELD_SCHEMA)]
)
That's what the Model.clean() method is for (see docs). Example:
class MyData(models.Model):
some_json = JSONField()
...
def clean(self):
if not is_my_schema(self.some_json):
raise ValidationError('Invalid schema.')
you could use cerberus to validate your data against a schema
from cerberus import Validator
schema = {'name': {'type': 'string'}}
v = Validator(schema)
data = {'name': 'john doe'}
v.validate(data) # returns "True" (if passed)
v.errors # this would return the error dict (or on empty dict in case of no errors)
it's pretty straightforward to use (also due to it's good documentation -> validation rules: http://docs.python-cerberus.org/en/stable/validation-rules.html)
I wrote a custom JSONField that extends models.JSONField and validates attribute's value by using jsonschema (Django 3.1, Python 3.7).
I didn't use the validators parameter for one reason: I want to let users define the schema dynamically.So I use a schema parameter, that should be:
None (by default): the field will behave like its parent class (no JSON schema validation support).
A dict object. This option is suitable for a small schema definition (for example: {"type": "string"});
A str object that describes a path to the file where the schema code is contained. This option is suitable for a big schema definition (to preserve the beauty of the model class definition code). For searching I use all enabled finders: django.contrib.staticfiles.finders.find().
A function that takes a model instance as an argument and returns a schema as dict object. So you can build a schema based on the state of the given model instance. The function will be called every time when the validate() is called.
myapp/models/fields.py
import json
from jsonschema import validators as json_validators
from jsonschema import exceptions as json_exceptions
from django.contrib.staticfiles import finders
from django.core import checks, exceptions
from django.db import models
from django.utils.functional import cached_property
class SchemaMode:
STATIC = 'static'
DYNAMIC = 'dynamic'
class JSONField(models.JSONField):
"""
A models.JSONField subclass that supports the JSON schema validation.
"""
def __init__(self, *args, schema=None, **kwargs):
if schema is not None:
if not(isinstance(schema, (bool, dict, str)) or callable(schema)):
raise ValueError('The "schema" parameter must be bool, dict, str, or callable object.')
self.validate = self._validate
else:
self.__dict__['schema_mode'] = False
self.schema = schema
super().__init__(*args, **kwargs)
def check(self, **kwargs):
errors = super().check(**kwargs)
if self.schema_mode == SchemaMode.STATIC:
errors.extend(self._check_static_schema(**kwargs))
return errors
def _check_static_schema(self, **kwargs):
try:
schema = self.get_schema()
except (TypeError, OSError):
return [
checks.Error(
f"The file '{self.schema}' cannot be found.",
hint="Make sure that 'STATICFILES_DIRS' and 'STATICFILES_FINDERS' settings "
"are configured correctly.",
obj=self,
id='myapp.E001',
)
]
except json.JSONDecodeError:
return [
checks.Error(
f"The file '{self.schema}' contains an invalid JSON data.",
obj=self,
id='myapp.E002'
)
]
validator_cls = json_validators.validator_for(schema)
try:
validator_cls.check_schema(schema)
except json_exceptions.SchemaError:
return [
checks.Error(
f"{schema} must be a valid JSON Schema.",
obj=self,
id='myapp.E003'
)
]
else:
return []
def deconstruct(self):
name, path, args, kwargs = super().deconstruct()
if self.schema is not None:
kwargs['schema'] = self.schema
return name, path, args, kwargs
#cached_property
def schema_mode(self):
if callable(self.schema):
return SchemaMode.DYNAMIC
return SchemaMode.STATIC
#cached_property
def _get_schema(self):
if callable(self.schema):
return self.schema
elif isinstance(self.schema, str):
with open(finders.find(self.schema)) as fp:
schema = json.load(fp)
else:
schema = self.schema
return lambda obj: schema
def get_schema(self, obj=None):
"""
Return schema data for this field.
"""
return self._get_schema(obj)
def _validate(self, value, model_instance):
super(models.JSONField, self).validate(value, model_instance)
schema = self.get_schema(model_instance)
try:
json_validators.validate(value, schema)
except json_exceptions.ValidationError as e:
raise exceptions.ValidationError(e.message, code='invalid')
Usage:
myapp/models/__init__.py
def schema(instance):
schema = {}
# Here is your code that uses the other
# instance's fields to create a schema.
return schema
class JSONSchemaModel(models.Model):
dynamic = JSONField(schema=schema, default=dict)
from_dict = JSONField(schema={'type': 'object'}, default=dict)
# A static file: myapp/static/myapp/schema.json
from_file = JSONField(schema='myapp/schema.json', default=dict)
Another solution using jsonschema for simple cases.
class JSONValidatedField(models.JSONField):
def __init__(self, *args, **kwargs):
self.props = kwargs.pop('props')
self.required_props = kwargs.pop('required_props', [])
super().__init__(*args, **kwargs)
def validate(self, value, model_instance):
try:
jsonschema.validate(
value, {
'schema': 'http://json-schema.org/draft-07/schema#',
'type': 'object',
'properties': self.props,
'required': self.required_props
}
)
except jsonschema.exceptions.ValidationError:
raise ValidationError(
f'Value "{value}" failed schema validation.')
class SomeModel(models.Model):
my_json_field = JSONValidatedField(
props={
'foo': {'type': 'string'},
'bar': {'type': 'integer'}
},
required_props=['foo'])