django-ninja
django-ninja copied to clipboard
Feature for optional auth
I am trying to implement globalauth. But without login, AnonymousUser get always 401 error. There is no option that anynomoususer can get response correctly. How do you think supporting optional auth without overriding call function. Simply, passing option auth_optional parameter.
class HttpBearer(HttpAuthBase, ABC):
openapi_scheme: str = "bearer"
header: str = "Authorization"
def __init__(self, auth_optional=False):
self.auth_optional=auth_optional
def __call__(self, request: HttpRequest) -> Optional[Any]:
headers = get_headers(request)
auth_value = headers.get(self.header)
if not auth_value:
if not self.auth_optional:
return None
else:
return AnonymousUser()
parts = auth_value.split(" ")
if parts[0].lower() != self.openapi_scheme:
if settings.DEBUG:
logger.error(f"Unexpected auth - '{auth_value}'")
return None
token = " ".join(parts[1:])
return self.authenticate(request, token)
How to use without 401 error.
auth=GlobalAuth(auth_optional=True)
Does it make sesnse? If there is better solution, let me know it.
I have stumbled onto this exact error.
This is how i implemented optional auth
https://github.com/baseplate-admin/CoreProject/blob/abc9387793ebeab2cc86b64b2cb005f1173e5962/backend/django_core/apps/api/auth.py#L30-L43
I have stumbled onto this exact error.
This is how i implemented optional auth
https://github.com/baseplate-admin/CoreProject/blob/abc9387793ebeab2cc86b64b2cb005f1173e5962/backend/django_core/apps/api/auth.py#L30-L43
I guess it has to be supported optional and documented officialy. But I should say "thank you for sharing"
This is what I've done to accomplish what you're likely looking for...
from django.http import HttpRequest
from ninja import NinjaAPI
from ninja.errors import AuthenticationError
api = NinjaAPI()
@api.exception_handler(AuthenticationError)
def authentication_error(request: HttpRequest, exc: AuthenticationError):
# Allow unauthenticated access to some endpoints
if request.resolver_match and request.resolver_match.url_name in ["my_unauthenticated_endpoint"]:
request.auth = None # type: ignore
return None
return api.create_response(request, {"detail": "Unauthorized"}, status=401)
Since ninja's internals throw an AuthenticationError exception when the specified authenticator returns None, you can handle that exception specifically and have it not return a response when you want to allow the request through unauthenticated.
@williammck Wow! I couldn't figure out this best solution I guess. But as like me, It's better to be documented this code or supported optional auth.
Thanks a lot!
Here's my version. Working correctly with TestClient
from django.contrib.admin.views.decorators import staff_member_required
from django.http import HttpRequest
from ninja import NinjaAPI
from ninja_extra.exceptions import AuthenticationFailed
class OptionalAuthError(AuthenticationFailed):
pass
class MyAuth(HttpAuthBase): # Or any AuthBase class for your case
def authenticate(request: HttpRequest, token: str) -> UserModel:
user = get_user_by_token(token)
if not user or not user.is_active:
raise OptionalAuthError
return user
api = NinjaAPI()
@api.exception_handler(OptionalAuthError)
def handle_authentication_error(request: HttpRequest, exc: OptionalAuthError) -> None:
pass
The expected DX for this would be something like, passing auth=[MyAuth(), None] and request.auth is None if None is called.