onehux-sso
A real, installable Django app wrapping OneHux Accounts' Authorization Code + PKCE flow against its real hosted login page — formalizing what the Django integration guide otherwise only shows as copy-paste example code.
Install
pip install -e /path/to/onehux_sso_client/django-package
(Not yet published to PyPI — install from source until that's decided.)
Two hosts — don't mix them up
accounts.onehux.com serves the hosted login/logout pages a browser is redirected to.
api-accounts.onehux.com serves the actual OAuth API your backend calls server-to-server.
This package keeps them as two separate settings (LOGIN_BASE_URL / API_BASE_URL) precisely
because collapsing them into one host was a real, confirmed bug in the original integration
guides (see the backend repo's README.md, ADR-070) — the wrong host doesn't error loudly, it
silently 404s.
Setup
-
Register a real confidential-client
Applicationin your OneHux Accounts Organization (Dashboard → Applications), with aredirect_uripointing at wherever you mount this package'scallback/URL, and yourpost_logout_redirect_uriregistered in that same list — OneHux Accounts validates both against the oneredirect_urislist, not two separate ones. -
Add to
INSTALLED_APPS:INSTALLED_APPS = [ ..., "onehux_sso", ]
-
Add the settings block:
ONEHUX_SSO = { "CLIENT_ID": "onehux_client_...", "CLIENT_SECRET": "onehux_secret_...", "REDIRECT_URI": "https://yourapp.example.com/auth/callback/", "POST_LOGOUT_REDIRECT_URI": "https://yourapp.example.com/auth/logged-out/", # Everything below is optional — these are the defaults: "LOGIN_BASE_URL": "https://accounts.onehux.com", "API_BASE_URL": "https://api-accounts.onehux.com", "SCOPE": "openid profile email", "LOGIN_SUCCESS_REDIRECT": "/", "LOGOUT_SUCCESS_REDIRECT": "/", "SESSION_ACCESS_TOKEN_KEY": "onehux_access_token", }
-
Wire the URLs:
# yourproject/urls.py from django.urls import include, path urlpatterns = [ ..., path("auth/", include("onehux_sso.urls")), ]
This gives you four real, working endpoints: /auth/login/, /auth/callback/,
/auth/logout/, and /auth/userinfo/ (a ready-to-use JSON endpoint your own frontend can call
with credentials: 'include', matching the BFF pattern — your frontend never talks to OneHux
directly).
Using the client directly
If you'd rather wire your own views instead of using the ones above:
from onehux_sso import OneHuxClient
client = OneHuxClient.from_settings()
pending = client.start_authorization()
# stash pending.state / pending.code_verifier in request.session, then:
# return HttpResponseRedirect(pending.authorization_url)
tokens = client.exchange_code(
code=request.GET["code"],
state=request.GET["state"],
expected_state=request.session["onehux_sso_state"],
code_verifier=request.session["onehux_sso_pkce_verifier"],
)
claims = client.get_userinfo(access_token=tokens.access_token)
logout_url = client.build_logout_url()
Public application launcher
GET /api/v1/organizations/{org_slug}/public-applications/ is a real, public, unauthenticated
platform endpoint — no client_id/client_secret involved, usable for any Organization by its
own slug, not just your own configured one. It returns only name/logo_url/home_url for
Applications that Organization has opted into public listing — a pure "what can I launch" list,
never a way to start a sign-in flow.
apps = client.get_public_applications(org_slug="onehux")
# [PublicApplication(name="ODS", logo_url="https://...", home_url="https://...")]
Rendering is entirely up to you — this package ships the data method only, no template or component. A plain, unstyled illustration (adapt this to your own design, don't copy it as-is):
{% for app in public_applications %}
<a href="{{ app.home_url }}">
<img src="{{ app.logo_url }}" alt="{{ app.name }}">
{{ app.name }}
</a>
{% endfor %}
Logging out — what actually happens, and how to hear about it immediately
Two distinct logout paths reach the platform's identical underlying session-revocation call
(POST /api/v1/sessions/me/logout/), and OneHux Accounts genuinely, immediately revokes the
platform-wide session either way — this was traced directly against the backend, not assumed.
What differs is how this app finds out:
- RP-initiated logout — the user clicks "log out" inside this app itself
(
client.build_logout_url()//auth/logout/). This app already knows: it's the one that clearedrequest.session[SESSION_ACCESS_TOKEN_KEY]and drove the redirect. Nothing further to do. - IdP-initiated logout — the user logs out of a different app, or directly at
accounts.onehux.com/dashboard. The platform-wide session is revoked immediately and correctly, exactly the same as the RP-initiated case — but this app only finds out if it's listening for it.
OneHux Accounts implements real OIDC Back-Channel Logout (spec:
openid-connect-backchannel-1_0)
to close that gap: BackchannelLogoutView receives a signed logout_token POST the instant any
session tied to this app is revoked, anywhere, and clears the matching local Django session
immediately — not on the next stale /userinfo call.
To turn this on:
- Mount the package's URLs as shown in Setup above —
BackchannelLogoutViewis already included at/auth/backchannel-logout/(adjust for whatever prefix you mounted at). - Register that exact URL with OneHux:
The response includesPATCH /api/v1/applications/{id}/backchannel-logout/ { "backchannel_logout_uri": "https://yourapp.example.com/auth/backchannel-logout/" }backchannel_logout_secretexactly once — this is a dedicated signing secret, deliberately not yourCLIENT_SECRET(the backend stores that only as a one-way hash and can never read it back to sign anything with it). - Set
ONEHUX_SSO['BACKCHANNEL_LOGOUT_SIGNING_SECRET']to that value.
Without steps 1–3, IdP-initiated logout is still real and immediate at the platform level — this
app just won't hear about it until its own next /userinfo call fails with TokenExpiredError,
bounded by the access token's 15-minute lifetime. With them wired up, both logout paths are
functionally immediate from this app's point of view too.
No refresh token today — this is real, not a bug
OneHux Accounts access tokens are a 15-minute, single-issue lifetime. This platform does not
currently issue a refresh token. client.get_userinfo() raises onehux_sso.TokenExpiredError
when the token has expired or been revoked — catch it and send the user back through
client.start_authorization() for a fresh login. There is no silent-refresh path to fall back
to; this package makes that explicit rather than hiding it behind a generic error.
Example project
See example/ for a complete, runnable Django project using this package end-to-end —
registered against a real disposable test Application and actually run through the full
browser flow against production, not just unit-tested in isolation.
License
Apache License 2.0 — see LICENSE.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file onehux_sso-0.1.0.tar.gz.
File metadata
- Download URL: onehux_sso-0.1.0.tar.gz
- Upload date:
- Size: 23.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
150a934b93143ee5ffedb8dd32adb2b9d28f5bac94291795c8e8d95badc345ea
|
|
| MD5 |
3f7d7259cb7e4f9379ea9d1a05f2898c
|
|
| BLAKE2b-256 |
870997916bbd0407b09fdcf1b7a89ba0ef659f231c6b9e267b3c270a88aff7d7
|
Provenance
The following attestation bundles were made for onehux_sso-0.1.0.tar.gz:
Publisher:
publish.yml on Onehux/onehux-sso-django
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
onehux_sso-0.1.0.tar.gz -
Subject digest:
150a934b93143ee5ffedb8dd32adb2b9d28f5bac94291795c8e8d95badc345ea - Sigstore transparency entry: 2511529491
- Sigstore integration time:
-
Permalink:
Onehux/onehux-sso-django@4de7c0febd43a7240338730b40af6b9237a3b5ff -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/Onehux
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@4de7c0febd43a7240338730b40af6b9237a3b5ff -
Trigger Event:
release
-
Statement type:
File details
Details for the file onehux_sso-0.1.0-py3-none-any.whl.
File metadata
- Download URL: onehux_sso-0.1.0-py3-none-any.whl
- Upload date:
- Size: 22.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f00d301bf52e3d397001aa2d3282f0b83b04f8f5d3a2280749d418eab160a89a
|
|
| MD5 |
0f1c36fa46df5ba50ff0aee4f872d74a
|
|
| BLAKE2b-256 |
20ca2bbb2c40827e8ee270f7afc127a5f2bf20e60e258cd0af81176734d3f739
|
Provenance
The following attestation bundles were made for onehux_sso-0.1.0-py3-none-any.whl:
Publisher:
publish.yml on Onehux/onehux-sso-django
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
onehux_sso-0.1.0-py3-none-any.whl -
Subject digest:
f00d301bf52e3d397001aa2d3282f0b83b04f8f5d3a2280749d418eab160a89a - Sigstore transparency entry: 2511529546
- Sigstore integration time:
-
Permalink:
Onehux/onehux-sso-django@4de7c0febd43a7240338730b40af6b9237a3b5ff -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/Onehux
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@4de7c0febd43a7240338730b40af6b9237a3b5ff -
Trigger Event:
release
-
Statement type: