anointedautomation-sso
Jesus is King ✝️
"Sign in with Anointed Automation" for Python: a server-side OpenID Connect kit with a framework-neutral core and thin
adapters for Django, FastAPI/Starlette and Flask. Authorization code + PKCE (S256), state, nonce, RS256 ID token
verification through the issuer's JWKS, refresh, revoke, userinfo, logout, back-channel logout (validation plus replay
protection) and a fail-open session status client. Python 3.10+, MIT.
Server only: the client holds your client secret and does the token exchange. Never ship it to a browser or a mobile app.
Get your client_id and client_secret from the partner portal; the full guide is
Sign in with Anointed Automation.
pip install anointedautomation-sso # core
pip install "anointedautomation-sso[flask]" # or [django], [fastapi]
ANOINTED_OAUTH_CLIENT_ID=aa_<your key id>
ANOINTED_OAUTH_CLIENT_SECRET=<server only>
ANOINTED_OAUTH_REDIRECT_URI=https://yourapp.example/auth/anointed/callback
Register the callback URL (exact https match) and, optionally, the back-channel logout URL on the portal's Sign in
tab. Nothing has a hidden default: a missing required setting raises AnointedSsoError with code config.
Core
from anointedautomation_sso import ANOINTED_ISSUER, AnointedClient, require_env
client = AnointedClient(
issuer=ANOINTED_ISSUER,
client_id=require_env("ANOINTED_OAUTH_CLIENT_ID"),
client_secret=require_env("ANOINTED_OAUTH_CLIENT_SECRET"),
redirect_uri=require_env("ANOINTED_OAUTH_REDIRECT_URI"),
# scope="openid email", # the default; add "profile" or "offline_access" only if you need them
)
auth = client.create_authorize_request(return_to="/account")
# 302 to auth.url and set the flow cookie: auth.cookie_name / auth.cookie_value / auth.cookie_options
# (or send auth.set_cookie_header as is).
result = client.handle_callback(query=request_query_dict, cookie_value=flow_cookie_value)
result.user.sub # key YOUR users on this
result.user.email # verified email only, trimmed and lowercase
result.return_to # safe same-site path or None
# Always clear the flow cookie afterwards (client.clear_flow_cookie_header()), on success and failure.
What handle_callback enforces: the flow cookie exists, state matches (constant time) before anything else, the flow is
not older than 10 minutes, the RFC 9207 iss parameter (when sent) is the issuer, then the code exchange with the PKCE
verifier and a fully verified ID token (RS256 only, iss, aud/azp, exp/iat with skew, nonce, sub, verified
email when the email scope is requested). Failures raise AnointedSsoError; branch on err.code
(state_mismatch, flow_expired, access_denied, id_token_invalid, email_not_verified, token_exchange, ...) and
never show str(err) to users.
Other calls: refresh(refresh_token, expected_sub=...) (a new refresh token each time; invalid_grant means signed out),
revoke(token), fetch_user_info(access_token), build_logout_url(id_token_hint=..., post_logout_redirect_uri=...),
take_new_user_password(sub) (the one-time password generated for a brand new account on your site; hash it yourself).
return_to is open-redirect safe: same-site paths only, unless you list allowed_return_origins (for example
["https://www.example.com", "https://*.example.com"]) or an is_allowed_return_to predicate. Even then it is https only.
Flask
from flask import Flask, redirect, session
from anointedautomation_sso import ANOINTED_ISSUER, AnointedClient, require_env
from anointedautomation_sso.adapters.flask import create_blueprint
app = Flask(__name__)
client = AnointedClient(
issuer=ANOINTED_ISSUER,
client_id=require_env("ANOINTED_OAUTH_CLIENT_ID"),
client_secret=require_env("ANOINTED_OAUTH_CLIENT_SECRET"),
redirect_uri=require_env("ANOINTED_OAUTH_REDIRECT_URI"),
)
def on_signed_in(result):
session["user_sub"] = result.user.sub # find or create YOUR user by sub
return redirect(result.return_to or "/")
def on_logout(logout):
... # end every session for logout.sub (or logout.sid)
app.register_blueprint(create_blueprint(client, on_signed_in=on_signed_in, on_logout=on_logout), url_prefix="/auth/anointed")
# GET /auth/anointed/start?returnTo=/account, GET /auth/anointed/callback, POST /auth/anointed/backchannel-logout
Django
# myapp/anointed.py
from django.contrib.auth import get_user_model, login
from django.http import HttpResponseRedirect
from anointedautomation_sso import ANOINTED_ISSUER, AnointedClient, require_env
from anointedautomation_sso.adapters.django import create_views
client = AnointedClient(
issuer=ANOINTED_ISSUER,
client_id=require_env("ANOINTED_OAUTH_CLIENT_ID"),
client_secret=require_env("ANOINTED_OAUTH_CLIENT_SECRET"),
redirect_uri=require_env("ANOINTED_OAUTH_REDIRECT_URI"),
)
def on_signed_in(request, result):
user, _ = get_user_model().objects.get_or_create(username=f"aa_{result.user.sub}", defaults={"email": result.user.email or ""})
login(request, user)
return HttpResponseRedirect(result.return_to or "/")
def on_logout(logout):
... # delete the sessions of logout.sub (or logout.sid)
views = create_views(client, on_signed_in=on_signed_in, on_logout=on_logout)
# urls.py
urlpatterns = [path("auth/anointed/", include(views.urls()))]
The back-channel route is CSRF-exempt by design (the provider posts without your cookies); everything on it is verified
by the Logout Token signature instead. Behind a proxy set SECURE_PROXY_SSL_HEADER as usual; the redirect URI is the
fixed value you configured, not derived from the request.
FastAPI / Starlette
from fastapi import FastAPI
from fastapi.responses import RedirectResponse
from anointedautomation_sso import ANOINTED_ISSUER, AnointedClient, require_env
from anointedautomation_sso.adapters.fastapi import create_router # Starlette: create_routes
app = FastAPI()
client = AnointedClient(
issuer=ANOINTED_ISSUER,
client_id=require_env("ANOINTED_OAUTH_CLIENT_ID"),
client_secret=require_env("ANOINTED_OAUTH_CLIENT_SECRET"),
redirect_uri=require_env("ANOINTED_OAUTH_REDIRECT_URI"),
)
async def on_signed_in(request, result):
request.session["user_sub"] = result.user.sub # needs SessionMiddleware, or use your own session
return RedirectResponse(result.return_to or "/", status_code=302)
async def on_logout(logout):
... # end every session for logout.sub (or logout.sid)
app.include_router(create_router(client, on_signed_in=on_signed_in, on_logout=on_logout), prefix="/auth/anointed")
Callbacks may be sync or async. The blocking core runs in a worker thread, so the event loop is never blocked. For plain
Starlette use create_routes(...) and mount the returned routes.
Back-channel logout
When someone signs out or disconnects your app, Anointed Automation POSTs a signed logout_token to your back-channel
logout URL. process_backchannel_logout (used by every adapter) validates it (RS256 via JWKS, typ: logout+jwt, iss,
aud, iat, the logout event, no nonce, a jti, sub and/or sid) and answers: 400 for a bad token, 200 for a
repeated jti (your handler is not run twice), 500 when the keys cannot be read or your handler raised (the provider
retries, and the jti is forgotten so the retry runs). The default replay store is in-memory; with several processes pass a
shared LogoutReplayStore (try_add(jti, remember_until) and remove(jti)) backed by Redis or your database.
Session status
End a local session when the user's Anointed Automation account or grant ends. It needs a partner API key with the
sessions:status permission (ask us to add it) and fails open: an outage never signs your users out.
from anointedautomation_sso import SessionStatusClient
status = SessionStatusClient(api_key=require_env("ANOINTED_API_KEY"), app_name="My App")
check = status.check(user_sub, grant=True)
if check.should_end_session:
... # sign the user out locally; check.reason says why
Security checklist
- Keep
client_secretand API keys in your secret store, never in source control, logs or a browser bundle. - Redirect URIs must be exact
httpsmatches; use an https tunnel or staging host to test. - Key your users on
sub, not on email. - Run the callback and the back-channel route over HTTPS only; send
Cache-Control: no-store(the adapters do). - Use a shared replay store when you run more than one process.
Development
python -m venv .venv && .venv/bin/pip install -e ".[test]" && .venv/bin/python -m pytest
Source: python/anointed-sso. The Node kit is @anointedautomation/sso and the .NET kit is AnointedAutomation.SSO.
Metadata
Release files for anointedautomation-sso 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| anointedautomation_sso-0.1.0.tar.gz | 36.9 kB | With provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| anointedautomation_sso-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 71.1 kB
Release files / anointedautomation_sso-0.1.0.tar.gz
| Download URL | anointedautomation_sso-0.1.0.tar.gz |
|---|---|
| Size | 36.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
d63df6edffd475338e6126de48b45fcc838bae7c8921898225a987ad2ec8e17f
|
|
BLAKE2b-256 checksum How to use checksums |
5d730cfb2003049ba9fba24f3928b05eb9d670f745ce18cc0aaed73cf2f7aaa6
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 24, 2026.
Transparency logRelease files / anointedautomation_sso-0.1.0-py3-none-any.whl
| Download URL | anointedautomation_sso-0.1.0-py3-none-any.whl |
|---|---|
| Size | 34.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
a3e6eda3f594eb831b9e827e80f45692b82e9bf97aaa7c2f7eae4446805bfa38
|
|
BLAKE2b-256 checksum How to use checksums |
949c633952fd3005cc93e398c338ee59ee81247ff4d680fc79787ec5b3dd59cc
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 24, 2026.
Transparency logSecurity for anointedautomation-sso 0.1.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for anointedautomation_sso-0.1.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 24, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.