Search code examples
authenticationtornadohttp-status-code-403

How can I make tornado web return 403 if current_user is None instead of redirect to login_url?


Following is the implementation of method authenticated:

def authenticated(
        method: Callable[..., Optional[Awaitable[None]]]
    ) -> Callable[..., Optional[Awaitable[None]]]:
        """Decorate methods with this to require that the user be logged in.

        If the user is not logged in, they will be redirected to the configured
        `login url <RequestHandler.get_login_url>`.

        If you configure a login url with a query parameter, Tornado will
        assume you know what you're doing and use it as-is.  If not, it
        will add a `next` parameter so the login page knows where to send
        you once you're logged in.
        """

        @functools.wraps(method)
        def wrapper(  # type: ignore
            self: RequestHandler, *args, **kwargs
        ) -> Optional[Awaitable[None]]:
            if not self.current_user:
                if self.request.method in ("GET", "HEAD"):
                    url = self.get_login_url()
                    if "?" not in url:
                        if urllib.parse.urlsplit(url).scheme:
                            # if login url is absolute, make next absolute too
                            next_url = self.request.full_url()
                        else:
                            assert self.request.uri is not None
                            next_url = self.request.uri
                        url += "?" + urlencode(dict(next=next_url))
                    self.redirect(url)
                    return None
                raise HTTPError(403)
            return method(self, *args, **kwargs)

        return wrapper

Seems there is no way to achieve what I want. When http method is GET, and if user didn't login, tornado will always redirect to login_url instead of just return 403.

Am I correct?


Solution

  • You can override the logic of getting user in your Base Handler

    class BaseHandler(RequestHandler):
        
        def get_current_user(self) -> Any:
            """Override to determine the current user from, e.g., a cookie.
    
            This method may not be a coroutine.
            """
            user = self.your_func_of_getting_user()
            if user is None:
                raise HTTPError(403)
            return user
    

    for async purposes you can do the next:

    class BaseHandler(RequestHandler):
        _current_user: 'SomeUserModel | None' = None
        ...
        
        async def prepare(self):
            self._current_user = await self.your_async_func()
            if self._current_user is None:
                raise HTTPError(403)