django-ninja icon indicating copy to clipboard operation
django-ninja copied to clipboard

Feature for optional auth

Open quroom opened this issue 2 years ago • 5 comments

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.

quroom avatar Jul 06 '23 02:07 quroom

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

baseplate-admin avatar Jul 06 '23 03:07 baseplate-admin

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"

quroom avatar Jul 06 '23 04:07 quroom

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 avatar Jul 11 '23 18:07 williammck

@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!

quroom avatar Jul 12 '23 02:07 quroom

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

nstonic avatar Aug 06 '24 07:08 nstonic

The expected DX for this would be something like, passing auth=[MyAuth(), None] and request.auth is None if None is called.

jleclanche avatar Nov 20 '24 03:11 jleclanche