drf-spectacular: [Possible Bug] Not being able to use custom DRF authentication
Describe the bug
I am trying to migrate my project from drf-yasg to drf-spectacular, but I am facing an issue when trying to add the same functionality I had in the old version.
While using drf-yasg the swagger-ui page presented a Login button which served as a way to block unauthorized users to see all the routes schema:
I want to achieve the same with drf-spectacular, if not possible to add the login button, at least make it work when I use an HTTP headers customization extension in the browser, like below:
To Reproduce Project authentication: My project is currently working with a token based authentication, the class is defined as below:
from doctorsystem.apps.equipe.models import DSToken
class DSTokenAuthentication(authentication.TokenAuthentication):
model = DSToken
DSToken model definition:
class DSToken(models.Model):
"""
The default authorization token model.
"""
key = models.CharField(max_length=40, primary_key=True)
mac_address = models.CharField(max_length=150, blank=True, null=True)
user = models.ForeignKey(
Equipe, blank=True, null=True, on_delete=models.CASCADE
)
cliente = models.ForeignKey(
Cliente, blank=True, null=True, on_delete=models.CASCADE
)
created = models.DateTimeField(auto_now_add=True)
class Meta:
verbose_name = "DSToken"
verbose_name_plural = "DSTokens"
def __str__(self):
return self.key
def save(self, *args, **kwargs):
if not self.key:
self.key = self.generate_key()
return super(DSToken, self).save(*args, **kwargs)
def generate_key(self):
return binascii.hexlify(os.urandom(20)).decode()
Previous configuration (drf-yasg): settings.py
REST_FRAMEWORK = {
"DEFAULT_RENDERER_CLASSES": ("rest_framework.renderers.JSONRenderer",),
"DEFAULT_PAGINATION_CLASS": (
"doctorsystem.apps.core.api.pagination.DSPagination"
),
"PAGE_SIZE": 100,
"DATE_INPUT_FORMATS": ["%d/%m/%Y", "%Y-%m-%d"],
"DATETIME_INPUT_FORMATS": [
"%d/%m/%Y %H:%M:%S",
"%d/%m/%Y %H:%M",
"%Y-%m-%d %H:%M:%S",
"%Y-%m-%d %H:%M",
],
"DATETIME_FORMAT": "%d/%m/%Y %H:%M:%S",
"DATE_FORMAT": "%d/%m/%Y",
"DEFAULT_FILTER_BACKENDS": (
"django_filters.rest_framework.DjangoFilterBackend",
),
}
SWAGGER_SETTINGS = {
"REFETCH_SCHEMA_WITH_AUTH": True,
"SECURITY_DEFINITIONS": {
"Basic": {"type": "basic"},
"Bearer": {"type": "apiKey", "name": "Authorization", "in": "header"},
},
"APIS_SORTER": "alpha",
"DOC_EXPANSION": "none",
"SHOW_REQUEST_HEADERS": True,
"SUPPORTED_SUBMIT_METHODS": ["get", "post", "put", "delete", "patch"],
"OPERATIONS_SORTER": "alpha",
"LOGIN_URL": "/login/",
"LOGOUT_URL": "/logout/",
}
urls.py
path(
"gds-api-docs/",
schema_view.with_ui("swagger", cache_timeout=0),
name="schema-swagger-ui",
),
path(
"gds-api-redocs/",
schema_view.with_ui("redoc", cache_timeout=0),
name="schema-redoc-ui",
),
New (last tried) configuration (drf-spectacular): settings.py
SPECTACULAR_SETTINGS = {
"TITLE": "Doctorsystem API",
"DESCRIPTION": "",
"VERSION": "1.0.0",
"SERVE_INCLUDE_SCHEMA": False,
"SCHEMA_PATH_PREFIX": "/api/",
"SERVE_PUBLIC": False,
"SERVE_AUTHENTICATION": [
"doctorsystem.apps.core.api.authentication.DSTokenAuthentication"
],
}
I also included the DEFAULT_SCHEMA_CLASS in my REST_FRAMEWORK object:
REST_FRAMEWORK = {
"DEFAULT_RENDERER_CLASSES": ("rest_framework.renderers.JSONRenderer",),
"DEFAULT_PAGINATION_CLASS": (
"doctorsystem.apps.core.api.pagination.DSPagination"
),
"PAGE_SIZE": 100,
"DATE_INPUT_FORMATS": ["%d/%m/%Y", "%Y-%m-%d"],
"DATETIME_INPUT_FORMATS": [
"%d/%m/%Y %H:%M:%S",
"%d/%m/%Y %H:%M",
"%Y-%m-%d %H:%M:%S",
"%Y-%m-%d %H:%M",
],
"DATETIME_FORMAT": "%d/%m/%Y %H:%M:%S",
"DATE_FORMAT": "%d/%m/%Y",
"DEFAULT_FILTER_BACKENDS": (
"django_filters.rest_framework.DjangoFilterBackend",
),
"DEFAULT_SCHEMA_CLASS": "drf_spectacular.openapi.AutoSchema",
}
urls.py:
path("schema/", SpectacularAPIView.as_view(), name="schema"),
path(
"spetacular-docs/",
SpectacularSwaggerView.as_view(url_name="schema"),
name="swagger-ui",
),
path(
"spetacular-redocs/",
SpectacularRedocView.as_view(url_name="schema"),
name="redocs-ui",
),
The error received with this configuration was:
app-api-django-1 | File "/usr/local/lib/python3.10/site-packages/rest_framework/views.py", line 332, in check_permissions
app-api-django-1 | if not permission.has_permission(request, self):
app-api-django-1 | File "/app/doctorsystem/apps/core/api/modules_permissions.py", line 148, in has_permission
app-api-django-1 | return request.session.get("has_agenda_online", False)
app-api-django-1 | File "/usr/local/lib/python3.10/site-packages/rest_framework/request.py", line 418, in __getattr__
app-api-django-1 | return self.__getattribute__(attr)
app-api-django-1 | AttributeError: 'Request' object has no attribute 'session'
It is being raised in the following custom Django REST permission:
class HasAgendaOnlinePermission(permissions.BasePermission):
def has_permission(self, request, view):
return request.session.get("has_agenda_online", False)
Which is used in some routes, like following:
class ConfiguracaoAgendaOnlineViewSet(viewsets.ModelViewSet):
permission_classes = (
IsAuthenticated,
CheckUserExpiredPermission,
ClienteBasePermission,
HasAgendaOnlinePermission,
)
authentication_classes = (DSTokenAuthentication,)
Important note: This error is raised when I add an Authorization header in my browser with the extension shown previously since the login button does not appear on the drf-spectacular swagger page.
I even tried adding an OpenApiAuthenticationExtension class in my settings.py, the error changed but I couldn’t proceed beyond that:
class MyAuthenticationScheme(OpenApiAuthenticationExtension):
target_class = (
"doctorsystem.apps.core.api.authentication.DSTokenAuthentication"
)
name = "MyAuth"
def get_security_definition(self, auto_schema):
return {
"type": "apiKey",
"in": "header",
"name": "Authorization",
}
Error:
app-api-django-1 | data=generator.get_schema(request=request, public=self.serve_public),
app-api-django-1 | File "/usr/local/lib/python3.10/site-packages/drf_spectacular/generators.py", line 268, in get_schema
app-api-django-1 | paths=self.parse(request, public),
app-api-django-1 | File "/usr/local/lib/python3.10/site-packages/drf_spectacular/generators.py", line 233, in parse
app-api-django-1 | assert isinstance(view.schema, AutoSchema), (
app-api-django-1 | AssertionError: Incompatible AutoSchema used on View <class 'doctorsystem.apps.apidev.viewsets.AgendaMedicoDiaView'>. Is DRF's DEFAULT_SCHEMA_CLASS pointing to "drf_spectacular.openapi.AutoSchema" or any other drf-spectacular compatible AutoSchema?
About this issue
- Original URL
- State: closed
- Created a year ago
- Comments: 20 (9 by maintainers)
Yes, the project didn’t include the DSTokenAuthentication in the REST_FRAMEWORK config, only directly in the routes, but it was time to fix it.
The override of the build_mock_request worked fine and solved the problem, now when I enter in the docs page, initially, I see only the public routes, and then once I paste my “token {token_key}” in the authorize form, the page reloads and I see all routes.
The only change in this last iteration was in my schema.py and settings.py - SPECTACULAR_SETTINGS: doctorsystem.apps.core.schema.py
settings.py
Thank you for all the support