Native OAuth / OpenID Connect sign-in, without a plugin #6
Labels
No labels
accessibility
authentication
breaking change
bug
documentation
enhancement
interface
internationalisation
observability
security
tier
1
tier
2
tier
3
tier/4
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
Postulo/postulo#6
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Observation
What exists today
Sign-in is by email and password only.
INSTALLED_APPSinsrc/postulo/config/settings/base.pyenablesallauthandallauth.account; nothing else.But the capability is already installed. django-allauth 65.19.2 — a dependency since M1 — ships
allauth.socialaccountwith a generic OpenID Connect provider (allauth.socialaccount.providers.openid_connect), which covers Authentik, Keycloak, Pocket ID, Zitadel, Kanidm, Google, and anything else that speaks OIDC, plus dedicated GitHub and GitLab providers. So "native" here means enabling and wiring what Postulo already depends on, not adding a plugin. One correction (2026-09-05): it does need allauth'ssocialaccountextra —requests,oauthlibandpyjwt[crypto]— which is not installed today;django-allauth[socialaccount]inpyproject.tomlis the whole of it. Good: this is the one place where the modularity principle should not apply — signing in is part of the core, and an instance must be able to do it with nothing extra installed.Shape
1. Enable
allauth.socialaccountwith the OpenID Connect provider, configured from the environment rather than the database, which is what allauth'sSOCIALACCOUNT_PROVIDERSsetting allows:Unset means no button, and nothing changes for anyone who does not want it. Room for a second, dedicated provider (GitHub, GitLab) later, but one generic OIDC entry covers every self-hosted identity provider that matters.
2. Account linking. An existing account with the same address as the identity provider reports should be linked, not duplicated. allauth's
SOCIALACCOUNT_EMAIL_AUTHENTICATIONandSOCIALACCOUNT_EMAIL_AUTHENTICATION_AUTO_CONNECTdo this, and they are safe only because the provider verified the address — which is exactly the case with OIDC. Postulo's own "Your details" page gets a Connected accounts section (allauth's connections view, in our shell like the email page).3. Provisioning versus invite-only. Postulo is invite-only by default, and
SOCIALACCOUNT_AUTO_SIGNUPwould let anyone the identity provider knows walk straight in. That is a decision, not an accident. The options, all defensible:Proposal: the first as the default, the second behind
POSTULO_OIDC_AUTO_SIGNUP=true, the third later if anyone asks.4. Forward-auth, the other SSO. For a self-hoster, "SSO" often means the reverse proxy authenticates and passes a header —
Remote-User,X-Forwarded-User— which is how oauth2-proxy and Authentik's outposts work, and what theoauth2-*containers on ragnar are doing. Django supports this natively withRemoteUserMiddlewareandRemoteUserBackend. Worth offering as a second, explicitly opt-in mode (POSTULO_TRUSTED_PROXY_HEADER), with the loud caveat that it is only safe when the proxy strips that header from incoming requests — a misconfiguration makes it a way to become anyone by sending one header. If offered, the docs have to say that in bold.5. SSO-only. Some operators will want to switch the password form off once SSO works. Cheap to add as
POSTULO_PASSWORD_LOGIN=falseonce the rest exists; not required for this issue.Interactions with the other 0.2.0 account work
preferred_username; map it on first sign-in, and fall back to the email local part when the claim is absent.name,given_name,family_name; populate on first sign-in so an SSO-created account is not born without a name.SOCIALACCOUNT_EMAIL_VERIFICATION). An SSO sign-in must not then be asked to click a verification link for the address it just proved.accounts/adapter.py,accounts/signals.py).Two gotchas, found before they cost an afternoon
src/postulo/config/settings/prod.pysetsform-action 'self'. allauth starts a provider login with a POST form that then redirects to the identity provider, and Chrome appliesform-actionto that redirect — so the sign-in button silently does nothing. Either add the provider's origin toform-action, or useSOCIALACCOUNT_LOGIN_ON_GET = Trueso the button is a plain link (allauth defaults to POST to guard against login CSRF; a GET is acceptable when the provider round-trip carries its ownstate).django.contrib.siteswhere installed — it is optional in this version). On the ragnar test instance, that ishttp://100.66.30.95:8000/…, and the IdP must be configured to accept a plain-HTTP redirect for it.ALLOWED_HOSTSandCSRF_TRUSTED_ORIGINSneed the same host.Classification
An enhancement. Not a breaking change: password sign-in stays; SSO is off until configured; no migration touches existing data (allauth's
socialaccounttables are new). If forward-auth is included, its opt-in flag is the only operator-facing surface, and it is the one that most needs a warning label.Open questions
SOCIALACCOUNT_STORE_TOKENS)? Postulo has no use for them today; storing credentials with no purpose is a liability. Proposal: no.