Skip to main content

nodus-auth

JWT issuance/validation, API key hashing, bcrypt passwords, and scoped principals for AI-native platforms.

Standalone auth primitives — no FastAPI required. All core functions work with any Python web framework or no framework at all.

Status: v0.2.0 — published on PyPI.

0.1.x is not supported. It signed and verified through python-jose, which carries an unfixed critical advisory (CVE-2026-85394). 0.2.0 moves to PyJWT>=2.15.1 and refuses asymmetric key material under an HMAC algorithm — see SECURITY.md.


Install

pip install nodus-auth

bcrypt version note: This package pins bcrypt>=4.0.1,<5.0 because passlib 1.7.4 is incompatible with bcrypt 5.x. Do not upgrade bcrypt beyond 4.x in the same environment.


What it provides

Component Purpose
KeyRing / create_access_token / decode_access_token JWT issuance and verification with key rotation
hash_password / verify_password bcrypt password hashing via passlib
generate_key / hash_key API key generation and SHA-256 storage hash
AuthPrincipal / Scopes Resolved identity with scope-based access control
LoginRequest / RegisterRequest / TokenResponse Pydantic request/response schemas
parse_user_id / require_user_id UUID parsing helpers
AuthSettings pydantic-settings config for SECRET_KEY, ALGORITHM, expiry

JWT tokens

from nodus_auth import AuthSettings, create_access_token, decode_access_token, InvalidTokenError

settings = AuthSettings(SECRET_KEY="my-secret-key-at-least-32-bytes-long")
token = create_access_token({"sub": "user-123"}, settings=settings)

try:
    payload = decode_access_token(token, settings=settings)
    user_id = payload["sub"]
except InvalidTokenError:
    # expired, malformed, or wrong key
    ...

Errors

Every error this package raises subclasses AuthError, so one except clause covers it and you never have to name a type from the JWT library underneath.

Error When Suggested response
InvalidTokenError expired, malformed, or signature failed 401
TokenCreationError the token could not be signed — e.g. an unsupported ALGORITHM 500; it is your configuration
InsecureKeyError asymmetric key material supplied for an HMAC algorithm 500, and fix it before serving — see SECURITY.md

TokenCreationError and InsecureKeyError are deliberately not InvalidTokenError: both mean the server is misconfigured, and answering 401 would blame the client for an operator's mistake. InsecureKeyError in particular means every token is forgeable, so it must not be absorbed by a 401 path.

Key rotation

from nodus_auth import KeyRing, create_access_token, decode_access_token, AuthSettings

key_v1 = "first-signing-key-at-least-32-bytes-long"
key_v2 = "second-signing-key-at-least-32-bytes-long"

ring = KeyRing(active=key_v1, grace_hours=24)
settings = AuthSettings(SECRET_KEY=key_v1)
token = create_access_token({"sub": "u1"}, settings=settings, key_ring=ring)

ring.rotate(key_v2)  # tokens signed with key_v1 still verify for 24 hours
payload = decode_access_token(token, settings=settings, key_ring=ring)  # OK

decode_access_token tries the active key first, then any keys within their grace period. Tokens outside their grace window raise InvalidTokenError.


Passwords

from nodus_auth import hash_password, verify_password

hashed = hash_password("correct-horse-battery-staple")
assert verify_password("correct-horse-battery-staple", hashed)
assert not verify_password("wrong-password", hashed)

API keys

from nodus_auth import generate_key, hash_key

raw_key, key_hash = generate_key()
# Deliver raw_key to the caller once; store key_hash in the database
assert hash_key(raw_key) == key_hash

generate_key() uses secrets.token_urlsafe. The raw key is never stored; the SHA-256 hash is used for all comparisons.


Scoped principals

from nodus_auth import AuthPrincipal, Scopes

principal = AuthPrincipal(
    user_id="user-123",
    auth_type="api_key",
    scopes=[Scopes.MEMORY_READ, Scopes.FLOW_EXECUTE],
)
assert principal.has_scope(Scopes.FLOW_EXECUTE)
assert not principal.has_scope(Scopes.PLATFORM_ADMIN)

auth_type is "jwt" or "api_key". Scopes provides well-known constants: MEMORY_READ, MEMORY_WRITE, FLOW_EXECUTE, FLOW_CREATE, PLATFORM_ADMIN.


AuthSettings

from nodus_auth import AuthSettings

# Reads from environment variables: SECRET_KEY, ALGORITHM, ACCESS_TOKEN_EXPIRE_MINUTES
settings = AuthSettings()

# Or explicit:
settings = AuthSettings(
    SECRET_KEY="...",
    ALGORITHM="HS256",
    ACCESS_TOKEN_EXPIRE_MINUTES=60,
)

User ID helpers

from nodus_auth import parse_user_id, require_user_id, parse_user_ids
import uuid

parse_user_id("550e8400-e29b-41d4-a716-446655440000")  # → UUID
parse_user_id(None)                                     # → None
require_user_id("not-a-uuid")                           # raises ValueError
parse_user_ids(["uid-1", "bad", "uid-2"])              # → [UUID, UUID]  (skips failures)

Dependencies

Package Version Purpose
PyJWT ≥2.15.1 JWT encoding/decoding (floor is security-relevant — see SECURITY.md)
passlib ≥1.7.4 bcrypt password context
bcrypt ≥4.0.1,<5.0 bcrypt backend (passlib 1.7.4 incompatible with 5.x)
pydantic ≥2.0.0 Schemas
pydantic-settings ≥2.14.2 AuthSettings from env vars

Development

pip install -e ".[dev]"
pytest tests/ -q

License

MIT — see LICENSE.

Metadata

Release files for nodus-auth 0.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for nodus-auth 0.2.0
File Size Uploaded
nodus_auth-0.2.0.tar.gz 24.0 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for nodus-auth 0.2.0
File Interpreter ABI Platform
nodus_auth-0.2.0-py3-none-any.whl Python 3 none any Details

Total release size: 38.2 kB

Release files / nodus_auth-0.2.0.tar.gz

Download URL nodus_auth-0.2.0.tar.gz
Size 24.0 kB
Tags Source
SHA-256 checksum
How to use checksums
d154deeac60c309bb348b28a813f31fa8249cf7719124b95731a55d48ac00485
BLAKE2b-256 checksum
How to use checksums
6caadb29eacb34470b3b52fb6e138354070483e4d60975eda25fe83f3fbacbe8
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.11.9

Release files / nodus_auth-0.2.0-py3-none-any.whl

Download URL nodus_auth-0.2.0-py3-none-any.whl
Size 14.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
e0a6033b9cff53a1095d1a21c0fa69ebf8c84f0e2ff433a9b9f84ad9cb142ced
BLAKE2b-256 checksum
How to use checksums
f74b7c8d4c775d61370b9f1e7873cfdd27c6b9b582705575527aad20e9d9077d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.11.9

Release history Release notifications | RSS feed

This release

0.2.0 This release

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page