Skip to main content

nyxa-auth — JWT / session auth & policies for Nyxa

Authentication and authorization for apps built with nyxa. Core nyxa does not depend on this package (no JWT in the core install).

At a glance

What you need Nyxa Auth
Protect routes Route.middleware(require_auth) or CurrentUser Depends
Current user get_current_user / CurrentUser / core user() stubs
Policies Policy subclasses under policies/ + authorize(...)
Hash / verify passwords hash_password / verify_password
JWT tokens create_access_token (AUTH_DRIVER=jwt, default)
Cookie sessions login_user / logout_user + configure_session (AUTH_DRIVER=session)
Personal access tokens (PATs) create_personal_token + set_token_lookup (Bearer alongside JWT)
OAuth (Google/GitHub) create_authorize_redirect / complete_oauth_login + oauth_identities (nyxa-auth[oauth])

Install

uv add nyxa-auth
uv add 'nyxa-auth[oauth]'   # Authlib — Google / GitHub OAuth
# JWT (default driver):
uv run nyxa init myapi --database sqlite --auth jwt --no-input
# Session cookies:
uv run nyxa init myapi --database sqlite --auth session --no-input

JWT driver

AUTH_DRIVER=jwt
AUTH_SECRET_KEY=change-me-to-a-long-random-secret!!
# AUTH_ALGORITHM=HS256
# AUTH_ACCESS_TOKEN_EXPIRE_MINUTES=60

Session driver

AUTH_DRIVER=session
AUTH_SECRET_KEY=change-me-to-a-long-random-secret!!
# AUTH_SESSION_SECRET=  # optional override for cookie signing

In main.py:

from nyxa_auth import configure_session, set_user_loader

app = FastAPI()
configure_session(app)
set_user_loader(...)

Login stores user_id in a signed cookie; require_auth reads it instead of Bearer JWT.

Personal access tokens

Requires nyxa-db. Under AUTH_DRIVER=jwt, Bearer auth tries JWT first, then a registered personal-token lookup.

from nyxa_auth import (
    create_personal_token,
    find_token,
    set_token_lookup,
    set_user_loader,
)

def lookup(plain: str) -> str | None:
    session = get_session_factory()()
    try:
        row = find_token(session, plain)
        if row is None:
            return None
        session.commit()
        return str(row.tokenable_id)
    finally:
        session.close()

set_user_loader(...)
set_token_lookup(lookup)

# Issue (plaintext once):
issued = create_personal_token(session, user.id, "ci")
# Authorization: Bearer nyxa_<id>_<secret>

Table: personal_access_tokens (hash only). Revoke with revoke_personal_token / revoke_all_for_user.

OAuth (Google + GitHub)

Requires the optional extra and Authlib. Identity rows need nyxa-db:

uv add 'nyxa-auth[oauth]'
OAUTH_GOOGLE_CLIENT_ID=...
OAUTH_GOOGLE_CLIENT_SECRET=...
OAUTH_GOOGLE_REDIRECT_URI=http://127.0.0.1:8000/api/oauth/google/callback
OAUTH_GITHUB_CLIENT_ID=...
OAUTH_GITHUB_CLIENT_SECRET=...
OAUTH_GITHUB_REDIRECT_URI=http://127.0.0.1:8000/api/oauth/github/callback
AUTH_SECRET_KEY=...   # also signs the OAuth state cookie
from nyxa_auth import (
    clear_oauth_state_cookie,
    complete_oauth_login,
    create_authorize_redirect,
    provider_from_env,
    resolve_oauth_identity,
)

provider = provider_from_env("google")  # or "github"
# GET /oauth/google/redirect → create_authorize_redirect(request, provider)
# callback → resolve_oauth_identity(...) then complete_oauth_login(...)
# JWT driver returns {"access_token", "token_type"}; session driver returns {"ok": true}

Users are linked by (provider, sub) in oauth_identities. On first login, email auto-link attaches the identity to an existing user when emails match (trusts the provider). Existing Google users get an identity row on their next OAuth login.

Quick start

from typing import Annotated

from fastapi import Depends, FastAPI
from nyxa import Route, register
from nyxa_auth import (
    CurrentUser,
    create_access_token,
    hash_password,
    require_auth,
    set_user_loader,
    verify_password,
)

app = FastAPI()

# Map JWT "sub" → your user object (sync callable).
users = {"1": {"id": 1, "email": "ada@example.com", "password_hash": hash_password("secret")}}
set_user_loader(lambda sub: users.get(sub))
register(app)

@app.post("/login")
def login(email: str, password: str) -> dict[str, str]:
    user = next((u for u in users.values() if u["email"] == email), None)
    if user is None or not verify_password(password, user["password_hash"]):
        from nyxa_auth import AuthenticationError
        raise AuthenticationError("Invalid credentials")
    return {"access_token": create_access_token(str(user["id"])), "token_type": "bearer"}

# Protect a route group:
with Route.prefix("/api"), Route.middleware(require_auth):
    Route.get("/me", lambda user: user)  # prefer a controller in real apps

Or inject the user on a controller action:

from nyxa_auth import CurrentUser

async def me(self, user: CurrentUser) -> dict:
    return {"id": getattr(user, "id", None)}

Policies

uv run nyxa make:policy User
from nyxa_auth import Policy, authorize

class UserPolicy(Policy):
    def update(self, user, model=None) -> bool:
        return model is not None and user.id == model.id

# In a controller:
authorize(user, UserPolicy, "update", target_user)

Denied abilities raise AuthorizationError (HTTP 403). Missing/invalid credentials raise AuthenticationError (HTTP 401).

Metadata

Release files for nyxa-auth 0.1.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 nyxa-auth 0.1.0
File Size Uploaded
nyxa_auth-0.1.0.tar.gz 16.5 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for nyxa-auth 0.1.0
File Interpreter ABI Platform
nyxa_auth-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 39.1 kB

Release files / nyxa_auth-0.1.0.tar.gz

Download URL nyxa_auth-0.1.0.tar.gz
Size 16.5 kB
Tags Source
SHA-256 checksum
How to use checksums
2c292f5177423bf7f9c0327762b09fcbc450dcec885ef3a0b8c838028a22ac27
BLAKE2b-256 checksum
How to use checksums
64f6a5629cb40ce88beb5adc8c9f283cc27eb230c988d53e7c5646dcadb2e3f9
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.20 {"installer":{"name":"uv","version":"0.12.20","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / nyxa_auth-0.1.0-py3-none-any.whl

Download URL nyxa_auth-0.1.0-py3-none-any.whl
Size 22.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
51ab2d7a504ef92f7feddfc0fcc669fbb930a1bc3722e6b65197bcd132eb0ebb
BLAKE2b-256 checksum
How to use checksums
d173a26359580beaa7580fe9f38964d0db1d2b44160ae8ef40bed2a0b08a9451
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.20 {"installer":{"name":"uv","version":"0.12.20","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release history Release notifications | RSS feed

This release

0.1.0 This release

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