Metadata-Version: 2.1
Name: drf-react-by-schema-oauth
Version: 0.1.2
Summary: OAuth 2.1 authorization server and resource server for drf-react-by-schema projects.
Home-page: https://gitlab.com/eita/drf-react-by-schema/drf-react-by-schema-oauth
Author: Cooperativa EITA
Author-email: coletivo@eita.org.br
License: GNU General Public License version 3
Classifier: Environment :: Web Environment
Classifier: Framework :: Django
Classifier: Framework :: Django :: 3.2
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Internet :: WWW/HTTP
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
Requires-Python: >=3.9
Description-Content-Type: text/x-rst
License-File: LICENSE

.. image:: https://gitlab.com/eita/drf-react-by-schema/drf-react-by-schema-oauth/-/raw/master/banner_drf-react-by-schema-oauth_2171x355.png
  :width: 1280
  :alt: DRF And React By Schema - OAuth

DRF And React By Schema - OAuth
===============================

OAuth 2.1 **authorization server + resource server** for projects built with
**DRF And React By Schema (DRBS)**.

It turns any DRBS Django/DRF project into a standard OAuth 2.1 provider — with
PKCE and Dynamic Client Registration (RFC 7591) — so **MCP clients** such as
`opencode <https://opencode.ai>`_ (and, later, the SPA/mobile app) can
authenticate through a **server-rendered Django login/consent page**, with
**no React changes**.

About the suite
---------------

*drf-react-by-schema* is a suite that builds headless Django + React
applications directly from your models:

* **drf-react-by-schema-django** (`PyPI <https://pypi.org/project/drf-react-by-schema/>`_) —
  generates the DRF API (serializers, viewsets, filters, schemas) from your
  models;
* **drf-react-by-schema-react** (`npm <https://www.npmjs.com/package/drf-react-by-schema>`_) —
  consumes those schemas to build the React forms, grids and CRUD interfaces;
* **drf-react-by-schema-oauth** (this package) — adds the **authorization
  layer** so clients can obtain and use OAuth 2.1 tokens against the same API;
* **drf-react-by-schema-mcp** — a generic, schema-driven **MCP server** that
  exposes a DRBS project to MCP clients, authorized by this package's tokens.

About this package
------------------

DRBS projects expose a rich DRF API generated from the Django models. This
package adds the missing authorization layer on top of
`django-oauth-toolkit (DOT) <https://django-oauth-toolkit.readthedocs.io>`_:

* an OAuth 2.1 **authorization server** (authorization code + PKCE S256, refresh
  token);
* **Dynamic Client Registration** (RFC 7591) so ``opencode mcp auth <sistema>``
  works with no manual client setup;
* **Authorization Server Metadata** (RFC 8414) and a helper to build **Protected
  Resource Metadata** (RFC 9728; served by the MCP package);
* a **DRF authentication class** and ``read``/``write`` **scope permissions**
  that authenticate both ``/mcp`` and ``/api/...`` with the same Bearer token;
* **server-rendered login/consent templates** that projects can override.

It is **additive and opt-in**: it does not replace Django auth or SimpleJWT, and
requires **no SPA work**.

Compatibility
-------------

The base DRBS package supports Django ≥ 3.2, and some deployments are still on
Django 3.2. Newer ``django-oauth-toolkit`` (3.x) requires Django ≥ 4.2, so this
package pins the **2.x** DOT line (``django-oauth-toolkit>=2.4,<3.0``), which
supports Django 3.2 through 5.x. (The suite's roadmap is to move consumer apps
to Django 5, at which point the DOT pin can be relaxed.)

Pre-requisites
--------------

* Django ≥ 3.2
* djangorestframework
* django-oauth-toolkit (2.x, installed automatically)

Quick start
-----------

1. Install:

   .. code-block:: bash

       pip install drf-react-by-schema-oauth

2. Add the DOT app and this package to ``INSTALLED_APPS`` (order matters —
   ``oauth2_provider`` first):

   .. code-block:: python

       INSTALLED_APPS = [
           ...
           'oauth2_provider',
           'drf_react_by_schema_oauth',
       ]

3. Include the URLs:

   .. code-block:: python

       from django.urls import include, path

       urlpatterns = [
           ...
           path('', include('drf_react_by_schema_oauth.urls')),
       ]

4. Migrate (creates DOT's token/application tables):

   .. code-block:: bash

       python manage.py migrate

That is the whole consumer contract: **two apps, one** ``include()``, **one**
``migrate``.

Accepting OAuth tokens on your API
----------------------------------

To let the **same** Bearer token authenticate ``/api/...`` (used by the SPA via
JWT and by MCP via OAuth), add the authentication class to your DRF settings or
per viewset:

.. code-block:: python

    REST_FRAMEWORK = {
        'DEFAULT_AUTHENTICATION_CLASSES': (
            # OAuth *first*: SimpleJWT raises AuthenticationFailed on a non-JWT
            # Bearer (e.g. an OAuth access token) and would stop the chain before
            # OAuth is tried. OAuth2Authentication returns None (not an error) for
            # non-OAuth tokens, so JWT/session still resolve afterwards.
            'drf_react_by_schema_oauth.authentication.OAuth2Authentication',
            'rest_framework_simplejwt.authentication.JWTAuthentication',
            'rest_framework.authentication.SessionAuthentication',
        ),
        ...
    }

Then add ``HasScope`` where you want OAuth tokens to be scope-limited (e.g. a
read-only token cannot write). It is **composable**: requests authenticated by
session/JWT are left to your own permissions, only OAuth tokens are
scope-checked.

.. code-block:: python

    from rest_framework.permissions import IsAuthenticated
    from drf_react_by_schema_oauth.permissions import HasScope

    class MyViewSet(viewsets.ModelViewSet):
        authentication_classes = [
            OAuth2Authentication,   # first, for the reason above
            JWTAuthentication,
            SessionAuthentication,
        ]
        permission_classes = [IsAuthenticated, HasScope]

A view can demand an explicit set instead of the default method mapping:

.. code-block:: python

    class ReportView(APIView):
        required_scopes = ['read']
        permission_classes = [IsAuthenticated, HasScope]

Settings
--------

All settings live in a single optional ``DRBS_OAUTH`` dict:

.. code-block:: python

    DRBS_OAUTH = {
        # Canonical public base URL of the authorization server (RFC 8414
        # `issuer`). Set it in production; when unset it is derived from the
        # request origin.
        'ISSUER': 'https://auth.example.org',

        # Scopes advertised, accepted and shown on the consent page (name ->
        # description). This is the single source of truth: the package feeds
        # these into DOT's consent rendering, so you do not need to duplicate
        # them in OAUTH2_PROVIDER['SCOPES'].
        'SCOPES': {
            'read': 'Read access to the API.',
            'write': 'Write access to the API.',
        },

        # Dynamic Client Registration.
        'DCR_ENABLED': True,
        'DCR_ALLOWED_REDIRECTS': [  # scheme+host prefixes; loopback by default
            'http://127.0.0.1',
            'http://localhost',
            'http://[::1]',
        ],

        # Optional template overrides (see "Templates" below).
        'LOGIN_TEMPLATE': None,     # defaults to "drbs_oauth/login.html"
        'CONSENT_TEMPLATE': None,   # defaults to "drbs_oauth/authorize.html"

        # Reserved.
        'AUDIENCE': None,
        'ACCESS_TOKEN_TTL': None,
        'REFRESH_TOKEN_TTL': None,
    }

Endpoints
---------

* ``/.well-known/oauth-authorization-server`` — Authorization Server Metadata (RFC 8414)
* ``/o/authorize/`` — authorization endpoint (login + consent)
* ``/o/token/`` — token endpoint
* ``/o/revoke_token/`` — token revocation (RFC 7009)
* ``/o/introspect/`` — token introspection (RFC 7662)
* ``/o/register/`` — Dynamic Client Registration (RFC 7591)
* ``/o/login/`` — server-rendered login page

Scopes
------

Two scopes are defined and advertised:

* ``read`` — safe methods (``GET``/``HEAD``/``OPTIONS``/``TRACE``);
* ``write`` — unsafe methods (``POST``/``PUT``/``PATCH``/``DELETE``).

The names are also exported as constants
(``drf_react_by_schema_oauth.SCOPE_READ`` / ``SCOPE_WRITE``).

Templates
---------

The login and consent pages are plain server-rendered HTML and require no build
step. Override them either by pointing ``DRBS_OAUTH`` at your own template names
(``LOGIN_TEMPLATE`` / ``CONSENT_TEMPLATE``) or by shadowing
``drbs_oauth/login.html`` / ``drbs_oauth/authorize.html`` from a project
template directory.

Using with MCP (opencode)
-------------------------

Once a project is deployed with this package (and the MCP package mounts
``/mcp``), an opencode user authorizes with:

.. code-block:: bash

    opencode mcp auth <sistema>

opencode:

1. discovers the authorization server (via the resource server's Protected
   Resource Metadata and/or ``/.well-known/oauth-authorization-server``);
2. registers a client dynamically at ``/o/register/``;
3. opens the **Django-rendered** login/consent page in the browser;
4. stores a **per-user** access/refresh token and calls ``/mcp`` with it.

No credential is ever centralized: each system authorizes its own users and
keeps Django as the permission authority.

Security notes
--------------

* Dynamic client registration creates **public** clients only (no client
  secret), and redirect URIs are restricted to **loopback** by default
  (RFC 8252); plain ``http`` is allowed only for loopback hosts.
* Access and refresh tokens are stored by DOT as its own hashed records — this
  package introduces no additional secret store.
* Set ``DRBS_OAUTH['ISSUER']`` in production so the advertised issuer does not
  depend on the incoming ``Host`` header.
* Introspection is protected by DOT and requires a Bearer token carrying the
  ``introspection`` scope.

License
-------

GNU General Public License v3 — see ``LICENSE``.
