Hello folks!
I've used this library in a lot of projects, and I very like its simplicity and effectivity.
Recently I've mentioned the small issue - default schema generation can't recognize response fields.
Part of generated OpenAPI schema for the Token Obtain request:
requestBody:
content:
application/json:
schema: &id001
properties:
username:
type: string
password:
type: string
writeOnly: true
required:
- username
- password
application/x-www-form-urlencoded:
schema: *id001
multipart/form-data:
schema: *id001
responses:
'200':
content:
application/json:
schema:
properties:
username:
type: string
required:
- username
description: ''
As can be seen, the schema states that the response contains only the username field, but actually, it should return other fields: access and refresh.
I've got a look at the sources and found that fields access and refresh are not mentioned in the serializer. Probably, we can mark username and password fields as write_only, and add access and refresh fields as read_only. This should make a trick.
Are you saying we should add the username to the response? The TokenObtainPairView should only be a POST HTTP method. The serializer does not include access and refresh because it uses the validate method in the view (i.e. using serializer.validated_data) to make new data, as you can see here: https://github.com/SimpleJWT/django-rest-framework-simplejwt/blob/ce80c2d31f0d9aff6a246d30d9aaa27f1e57a364/rest_framework_simplejwt/serializers.py#L70
On L71, you get an empty dict if the authentication was successful, as can be seen here: https://github.com/SimpleJWT/django-rest-framework-simplejwt/blob/ce80c2d31f0d9aff6a246d30d9aaa27f1e57a364/rest_framework_simplejwt/serializers.py#L58
So, in theory, the access and refresh token are read only and the user credentials are still write only.
Thank you for your reply.
I wanted to say that the current behavior is that the response contains two fields - access and refresh, but DRF's automatically generated schema states the response field is username (which is not true).
It is a bit confusing (in my cases) for front-end developers when they look at the generated schema and see invalid response fields.
Also, I suggested that explicitly adding access and refresh fields to the serializer could do the trick. I can check this suggestion a bit later.
I see. I do mostly full stack, so I generally just write some documentation of the response json. I suppose it's possible we can change the serializer's behavior, which would probably mean fragmenting that main serializer.
@regzon Sorry it's been awhile. Taking a retrospective look at serializers.py, the way TokenObtainSerializer is setup with the __init__ magic method has to be like that due to a dynamic self.username_field field name. If we made a class name stating User.USERNAME_FIELD = serializers.CharField(). It may not work. I'll write some test case at some point to see if it's feasible to do that, but I slightly doubt it.
Here's a simple workaround if you're using drf-yasg:
@method_decorator(
name='post',
decorator=swagger_auto_schema(
responses={
status.HTTP_200_OK: openapi.Schema(
title='TokenPair',
type='object',
properties={
'refresh': openapi.Schema(title='Refresh token', type='string'),
'access': openapi.Schema(title='Access token', type='string'),
},
required=['access', 'refresh']
)
},
)
)
class MyTokenView(TokenObtainPairView):
pass
@method_decorator(
name='post',
decorator=swagger_auto_schema(
responses={
status.HTTP_200_OK: openapi.Schema(
title='AccessToken',
type='object',
properties={
'access': openapi.Schema(title='Access token', type='string')
},
required=['access']
)
}
)
)
class MyRefreshView(TokenRefreshView):
pass
Here's a simple workaround if you're using drf-yasg:
@method_decorator( name='post', decorator=swagger_auto_schema( responses={ status.HTTP_200_OK: openapi.Schema( title='TokenPair', type='object', properties={ 'refresh': openapi.Schema(title='Refresh token', type='string'), 'access': openapi.Schema(title='Access token', type='string'), }, required=['access', 'refresh'] ) }, ) ) class MyTokenView(TokenObtainPairView): pass @method_decorator( name='post', decorator=swagger_auto_schema( responses={ status.HTTP_200_OK: openapi.Schema( title='AccessToken', type='object', properties={ 'access': openapi.Schema(title='Access token', type='string') }, required=['access'] ) } ) ) class MyRefreshView(TokenRefreshView): pass
i am using drf-yasg。
And some imperfections
thinks, Inspired me。
@Andrew-Chen-Wang @regzon @mithodin
This is my ultimately correct:
created xxx/xxx.py:
from rest_framework_simplejwt.views import TokenObtainPairView, TokenRefreshView
@method_decorator(
name='post',
decorator=swagger_auto_schema(
operation_summary='授权获取',
operation_description='使用账号密码获取token',
request_body=openapi.Schema(
type=openapi.TYPE_OBJECT,
properties={
'username': openapi.Schema(
type=openapi.TYPE_STRING,
description='用户名'
),
'password': openapi.Schema(
type=openapi.TYPE_STRING,
description='密码'
)
}
),
responses={
"200": openapi.Response(
description="success!",
schema=openapi.Schema(
type=openapi.TYPE_OBJECT,
properties={
'access': openapi.Schema(
type=openapi.TYPE_STRING,
description='身份token'
),
'refresh': openapi.Schema(
type=openapi.TYPE_STRING,
description='刷新token的refresh'
),
}
),
),
}
)
)
class MyTokenView(TokenObtainPairView):
pass
@method_decorator(
name='post',
decorator=swagger_auto_schema(
operation_summary='授权刷新',
operation_description='token过期后,使用refresh刷新token',
request_body=openapi.Schema(
type=openapi.TYPE_OBJECT,
properties={
'refresh': openapi.Schema(
type=openapi.TYPE_STRING,
description='refresh'
)
}
),
responses={
"200": openapi.Response(
description="success!",
schema=openapi.Schema(
type=openapi.TYPE_OBJECT,
properties={
'access': openapi.Schema(
type=openapi.TYPE_STRING,
description='身份token'
),
'refresh': openapi.Schema(
type=openapi.TYPE_STRING,
description='刷新token的refresh'
),
}
),
),
}
)
)
class MyRefreshView(TokenRefreshView):
pass
url.py
from xxx.xxx import MyTokenView, MyRefreshView
urlpatterns = [
path('auth/token/', MyTokenView.as_view(), name='token_obtain_pair'),
path('auth/token/refresh/', MyRefreshView.as_view(), name='token_refresh'),
]
Most helpful comment
Here's a simple workaround if you're using drf-yasg: