django-ninja: Authentication is never awaited in AsyncOperation
Example
from asgiref.sync import sync_to_async
from ninja import NinjaAPI, Schema
from ninja.security import APIKeyQuery
from .models import Client
api = NinjaAPI()
class ApiKey(APIKeyQuery):
param_name = "key"
async def authenticate(self, request, key):
try:
return await sync_to_async(Client.objects.get)(key=key)
except Client.DoesNotExist:
pass
class Test(Schema):
msg: str
@api.get("", response=Test, auth=ApiKey())
async def test(request):
return 200, {"msg": "hello!"}
Scenario
The API is called with a non-existing API key. What should happen: Ninja should return a 401 response. What actually happens: Returns 200.
Possible solution
Implement _run_checks and _run_authentication as async methods in AsyncOperation.
class AsyncOperation(Operation):
def __init__(self, *args, **kwargs):
if django.VERSION < (3, 1): # pragma: no cover
raise Exception("Async operations are supported only with Django 3.1+")
super().__init__(*args, **kwargs)
self.is_async = True
async def _run_checks(self, request):
"Runs security checks for each operation"
# auth:
if self.auth_callbacks:
error = await self._run_authentication(request)
if error:
return error
# csrf:
if self.api.csrf:
error = check_csrf(request, self.view_func)
if error:
return error
async def _run_authentication(self, request):
for callback in self.auth_callbacks:
result = await callback(request)
if result is not None:
request.auth = result
return
return Response({"detail": "Unauthorized"}, status=401)
async def run(self, request, **kw):
error = await self._run_checks(request)
if error:
return error
values, errors = self._get_values(request, kw)
if errors:
return Response({"detail": errors}, status=422)
result = await self.view_func(request, **values)
return self._create_response(result)
Above example will then return 401 on an invalid key as expected.
About this issue
- Original URL
- State: closed
- Created 4 years ago
- Reactions: 3
- Comments: 16 (10 by maintainers)
Commits related to this issue
- fixed empty token in async authentication (#44) — committed to vitalik/django-ninja by vitalik 8 months ago
Not sure if this helps, but… try adding an await statement
Thanks @changhyun-an and @maxmorlocke! I build a decorator from your code 😃 This should reduce some boilerplate in case of many endpoints
Usage:
For the full copypasta
@vitalik I’d like to help with this. I don’t understand what’s wrong with #202 though. It seems that it’s very similar to the code above plus taking care of async auth on not-async operation and vice versa. If I understand what needs to be changed compared to #202, I’d be happy to prepare PR with code, tests and docs changes.
One of the main reasons I chose
django-ninja
was it’s support for async views and I’d like to broaden the async experience even further. Right now, I can’t use built-inauth
at all and I’m doomed to calling my async auth function in each view, which is repetitive and prone to errors.@skokado please check with latest version
@skokado
could you provide your example code ?
Is there any chance for this issue to be resolved soon? I’m quite fond of django-ninja, it’s just this issue that I find a bit unsettling.
Yes, in nutshell this is the code I’m targeting for… but there are few things to deal with - like if you have async auth, but not-async operation or vice-versa