Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion scripts/audit-api-auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,9 @@
# Format: (module_dir, router_file_basename, function_name)
# Keep this list small and review changes carefully.
ALLOWED_PUBLIC = {
# OAuth token endpoint - public by design
# OAuth token endpoints - public by design
("spp_api_v2", "oauth.py", "get_token"),
("spp_api_v2_oauth", "oauth_rs256.py", "get_rs256_token"),
# Capability/metadata discovery - public by design
("spp_api_v2", "metadata.py", "get_metadata"),
# DCI callback endpoints - called by external systems
Expand Down
228 changes: 228 additions & 0 deletions spp_api_v2_oauth/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
==================================
OpenSPP API V2: OAuth RS256 Bridge
==================================

..
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:fcf958693834e280f3eddcb2b6e8050b5c48b1cf04ca7f64638746fb22ed8af8
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
:target: https://odoo-community.org/page/development-status
:alt: Beta
.. |badge2| image:: https://img.shields.io/badge/license-LGPL--3-blue.png
:target: http://www.gnu.org/licenses/lgpl-3.0-standalone.html
:alt: License: LGPL-3
.. |badge3| image:: https://img.shields.io/badge/github-OpenSPP%2FOpenSPP2-lightgray.png?logo=github
:target: https://github.com/OpenSPP/OpenSPP2/tree/19.0/spp_api_v2_oauth
:alt: OpenSPP/OpenSPP2

|badge1| |badge2| |badge3|

Bridge module that enables RS256 (asymmetric RSA) JWT authentication for
the OpenSPP API V2. Automatically installed when both ``spp_api_v2`` and
``spp_oauth`` are present.

What It Does
~~~~~~~~~~~~

- Adds RS256 token verification alongside existing HS256 support — both
algorithms are accepted simultaneously
- Provides a dedicated ``/oauth/token/rs256`` endpoint for generating
RS256-signed JWT tokens
- Routes incoming tokens to the correct verification path based on the
JWT header's ``alg`` field
- Enforces the same security controls as HS256: audience, issuer, and
expiration validation

When To Use RS256
~~~~~~~~~~~~~~~~~

RS256 uses asymmetric RSA keys (public/private pair) instead of a shared
secret:

- **Distributed deployments**: External systems can verify tokens using
only the public key, without access to the signing secret
- **Zero-trust architectures**: The private key never leaves the token
issuer
- **Regulatory compliance**: Some security standards require asymmetric
signing

How It Works
~~~~~~~~~~~~

+-----------------+----------------------------------------------------+
| Token Algorithm | Verification Path |
+=================+====================================================+
| RS256 | RSA public key from ``spp_oauth`` settings + |
| | audience/issuer/expiry validation |
+-----------------+----------------------------------------------------+
| HS256 | Original ``spp_api_v2`` shared-secret verification |
| | (unchanged) |
+-----------------+----------------------------------------------------+

The bridge replaces the ``get_authenticated_client`` FastAPI dependency
via ``dependency_overrides``. All existing API endpoints automatically
support both algorithms — no router changes needed.

Dependencies
~~~~~~~~~~~~

============== =======================================================
Module Role
============== =======================================================
``spp_api_v2`` Provides the REST API, HS256 auth, and API client model
``spp_oauth`` Provides RSA key storage and retrieval utilities
============== =======================================================

Configuration
~~~~~~~~~~~~~

1. Configure RSA keys in **Settings > General Settings > SPP OAuth
Settings**
2. The bridge activates automatically — existing HS256 clients continue
to work unchanged
3. Use ``/oauth/token/rs256`` to obtain RS256-signed tokens

**Table of contents**

.. contents::
:local:

Usage
=====

Prerequisites
~~~~~~~~~~~~~

- ``spp_api_v2`` and ``spp_oauth`` modules installed (bridge
auto-installs)
- RSA key pair generated and configured in SPP OAuth Settings
- An API client created in ``spp_api_v2`` with appropriate scopes

Generate RSA Keys
~~~~~~~~~~~~~~~~~

.. code:: bash

openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:4096 -out private.pem
openssl rsa -in private.pem -pubout -out public.pem

Configure the keys in **Settings > General Settings > SPP OAuth
Settings**.

Obtain an RS256 Token
~~~~~~~~~~~~~~~~~~~~~

.. code:: bash

curl -X POST https://your-instance/api/v2/spp/oauth/token/rs256 \
-H "Content-Type: application/json" \
-d '{
"grant_type": "client_credentials",
"client_id": "client_abc123",
"client_secret": "your-client-secret"
}'

Response:

.. code:: json

{
"access_token": "eyJhbGciOiJSUzI1NiIs...",
"token_type": "Bearer",
"expires_in": 86400,
"scope": "individual:read group:read"
}

Use the Token
~~~~~~~~~~~~~

.. code:: bash

curl https://your-instance/api/v2/spp/Individual/urn:test%23ID-001 \
-H "Authorization: Bearer eyJhbGciOiJSUzI1NiIs..."

The API automatically detects RS256 tokens from the JWT header and
verifies them with the configured RSA public key.

Existing HS256 Clients
~~~~~~~~~~~~~~~~~~~~~~

No changes needed. Tokens obtained from the original ``/oauth/token``
endpoint continue to work. The bridge accepts both RS256 and HS256
tokens simultaneously, routing based on the ``alg`` field in the JWT
header.

Verify Token Algorithm
~~~~~~~~~~~~~~~~~~~~~~

To confirm which algorithm a token uses, decode the JWT header (without
verification):

.. code:: python

import jwt
header = jwt.get_unverified_header(token)
# header["alg"] will be "RS256" or "HS256"

Error Responses
~~~~~~~~~~~~~~~

+---------------------------+-------------+---------------------------+
| Scenario | HTTP Status | Detail |
+===========================+=============+===========================+
| RSA keys not configured | 400 | "RS256 token generation |
| | | not available..." |
+---------------------------+-------------+---------------------------+
| Invalid credentials | 401 | "Invalid client |
| | | credentials" |
+---------------------------+-------------+---------------------------+
| Expired token | 401 | "Token expired" |
+---------------------------+-------------+---------------------------+
| Invalid signature | 401 | "Invalid token" |
+---------------------------+-------------+---------------------------+
| Unsupported algorithm | 401 | "Unsupported token |
| | | algorithm: {alg}" |
+---------------------------+-------------+---------------------------+
| Rate limit exceeded | 429 | "Rate limit exceeded" |
+---------------------------+-------------+---------------------------+

Bug Tracker
===========

Bugs are tracked on `GitHub Issues <https://github.com/OpenSPP/OpenSPP2/issues>`_.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us to smash it by providing a detailed and welcomed
`feedback <https://github.com/OpenSPP/OpenSPP2/issues/new?body=module:%20spp_api_v2_oauth%0Aversion:%2019.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.

Do not contact contributors directly about support or help with technical issues.

Credits
=======

Authors
-------

* OpenSPP.org

Maintainers
-----------

.. |maintainer-jeremi| image:: https://github.com/jeremi.png?size=40px
:target: https://github.com/jeremi
:alt: jeremi
.. |maintainer-gonzalesedwin1123| image:: https://github.com/gonzalesedwin1123.png?size=40px
:target: https://github.com/gonzalesedwin1123
:alt: gonzalesedwin1123

Current maintainers:

|maintainer-jeremi| |maintainer-gonzalesedwin1123|

This module is part of the `OpenSPP/OpenSPP2 <https://github.com/OpenSPP/OpenSPP2/tree/19.0/spp_api_v2_oauth>`_ project on GitHub.

You are welcome to contribute.
1 change: 1 addition & 0 deletions spp_api_v2_oauth/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import models
21 changes: 21 additions & 0 deletions spp_api_v2_oauth/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# pylint: disable=pointless-statement
{
"name": "OpenSPP API V2: OAuth RS256 Bridge",
"summary": "Bridges spp_api_v2 and spp_oauth to enable RS256 JWT authentication for the API.",
"category": "OpenSPP/Integration",
"version": "19.0.1.0.0",
"author": "OpenSPP.org",
"development_status": "Beta",
"maintainers": ["jeremi", "gonzalesedwin1123"],
"external_dependencies": {"python": ["pyjwt>=2.4.0", "cryptography"]},
"website": "https://github.com/OpenSPP/OpenSPP2",
"license": "LGPL-3",
"depends": [
"spp_api_v2",
"spp_oauth",
],
"data": [],
"application": False,
"auto_install": ["spp_api_v2", "spp_oauth"],
"installable": True,
}
8 changes: 8 additions & 0 deletions spp_api_v2_oauth/constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Part of OpenSPP. See LICENSE file for full copyright and licensing details.
"""Shared constants for the OAuth RS256 bridge module.

These must match the values used by spp_api_v2 in auth.py and oauth.py.
"""

JWT_AUDIENCE = "openspp"
JWT_ISSUER = "openspp-api-v2"
Empty file.
Loading
Loading