Django Ninja
============
Django OAuth Toolkit provides a support layer for
`Django Ninja `_.
This consists of a ``HttpOAuth2`` class, which will determine whether the
incoming HTTP request contains a valid OAuth2 access token issued
by Django OAuth Toolkit. Optionally, ``HttpOAuth2`` can ensure that the
OAuth2 Access Token contains a defined set of scopes.
Import ``HttpOAuth2`` as:
.. code-block:: python
from oauth2_provider.contrib.ninja import HttpOAuth2
Basic Usage
-----------
``HttpOAuth2`` can be used anywhere that
`Django Ninja expects an authentication callable `_.
For example, to ensure all requests are authenticated with OAuth2:
.. code-block:: python
from ninja import NinjaAPI
from oauth2_provider.contrib.ninja import HttpOAuth2
api = NinjaAPI(auth=HttpOAuth2())
To require authentication on only a single endpoint:
.. code-block:: python
from ninja import NinjaAPI
from oauth2_provider.contrib.ninja import HttpOAuth2
api = NinjaAPI()
@api.get("/private", auth=HttpOAuth2())
def private_endpoint(request):
return {"message": "This is a private endpoint"}
Optional Authentication
-----------------------
``HttpOAuth2`` will always fail if the request is not authenticated.
However, many use cases require optional authentication (for example, where
additional private content is returned for authenticated users).
Django Ninja's support for
`multiple authenticators `_
can be used for optional authentication. Simply place ``HttpOAuth2`` at the
beginning of a list of authenticators (where it will be run first), with more
permissive authenticator functions near the end (as a fall-back).
For example, to attempt OAuth2 authentication on all requests,
but allow access even for unauthenticated requests:
.. code-block:: python
from ninja import NinjaAPI
from oauth2_provider.contrib.ninja import HttpOAuth2
# Stricter authenticators must be placed first,
# as the first success terminates the chain
api = NinjaAPI(auth=[HttpOAuth2(), lambda _request: True])
Scope Enforcement
-----------------
``HttpOAuth2`` can optionally enforce that the OAuth2 access token has certain
scopes (defined by the application).
If a ``scopes`` argument is passed to ``HttpOAuth2``, then incoming access
tokens must contain all of the specified scopes to be considered valid.
For example:
.. code-block:: python
from ninja import NinjaAPI
from oauth2_provider.contrib.ninja import HttpOAuth2
api = NinjaAPI()
@api.post("/thing", auth=HttpOAuth2(scopes=["read", "write"]))
def create_endpoint(request):
...
Custom Authorization Behavior
-----------------------------
``HttpOAuth2`` can be extended to provide custom authorization behaviors.
Simply subclass it and override its ``authenticate`` method.
.. autoclass:: oauth2_provider.contrib.ninja.HttpOAuth2
:members: authenticate
For example:
.. code-block:: python
from typing import Any
from django.http import HttpRequest
from ninja import NinjaAPI
from oauth2_provider.contrib.ninja import HttpOAuth2
from oauth2_provider.models import AbstractAccessToken
class StaffOnlyOAuth2(HttpOAuth2):
def authenticate(self, request: HttpRequest, access_token: AbstractAccessToken) -> Any | None:
if not access_token.user.is_staff:
return None
# Anything truthy can be returned, and will be available as `request.auth`
return access_token
api = NinjaAPI(auth=StaffOnlyOAuth2())