OIDC SSO for Paca: Identity, Security, and Admin Configuration #431
Closed
xiandan-erizo
started this conversation in
General
Replies: 1 comment
|
Hi @xiandan-erizo , |
0 replies
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Status: Implemented in draft pull request #432.
Implementation status: The
feat/oidc-ssobranch now implements the completeproposal: the core OIDC flow, durable identity and safe JIT provisioning, Paca
sessions and authorization boundaries, protected admin configuration, encrypted
database persistence, live single-process reconfiguration, and Compose/Helm
bootstrap.
Summary
Paca should support one standards-compliant OpenID Connect provider per
installation for human sign-in. OIDC proves the person's external identity;
Paca remains responsible for users, sessions, authorization, API keys, Agent
credentials, and audit-relevant state.
The current branch configures OIDC through deployment environment variables.
This proposal completes the product experience by adding a protected Single
sign-on section to the admin settings page. A valid configuration is checked
with OIDC Discovery, encrypted, persisted, and activated in the current API
process when the administrator saves it.
Proposed Decisions
(issuer, subject)from the verified ID tokenUSERENCRYPTION_KEY; never plaintext fallback for OIDC secretsMotivation
Paca currently authenticates human users with local usernames and passwords.
Organizations commonly need their existing identity provider to enforce
central account lifecycle, MFA, and sign-in policy. A generic OIDC integration
covers providers such as Keycloak, Microsoft Entra ID, Okta, and Authentik
without coupling Paca to one vendor.
The protocol alone is not a complete product experience. Requiring an
administrator to edit container environment variables for every issuer,
client, label, or local-login change makes routine identity configuration an
infrastructure operation. The final experience should be manageable from the
same admin surface as other installation-wide settings while preserving a
safe deployment bootstrap and recovery path.
Design Principles
Global RBAC remains authoritative.
(iss, sub)pair identifies an account. Email, display name, and usernameare attributes.
ID tokens are not stored, returned to the SPA, or given to Agents.
configuration, missing encryption, and unsafe SSO-only transitions fail
closed while the last active configuration remains in service.
form, one runtime snapshot, and no distributed configuration protocol.
Goals
Non-Goals
This proposal does not include:
Trust And Credential Boundaries
The feature applies only to interactive human login.
The SPA does not perform the code exchange and never receives the OIDC client
secret, authorization code, access token, refresh token, or ID token.
Architecture
Valkey stores only the short-lived, single-use login transaction
state -> {nonce, PKCE verifier}. PostgreSQL stores the durable externalidentity binding and, after this proposal, encrypted administrative OIDC
configuration.
Login Flow
1. Login Entry-Point Discovery
The login page calls the public endpoint:
It returns display-only state:
{ "data": { "local_login_enabled": true, "oidc": { "enabled": true, "display_name": "Company SSO" } } }When OIDC is enabled, the page renders
Continue with Company SSO. When locallogin is disabled, it hides the password form. The backend also enforces that
policy in the auth service; hiding a form is not the security boundary.
2. Begin Login
The browser navigates to:
Paca generates cryptographically random state, nonce, and PKCE verifier
values. It stores the nonce and verifier in Valkey under the state value for
10 minutes, writes the same state to a short-lived HttpOnly
SameSite=Laxcookie scoped to the callback path, and redirects to the provider's
authorization endpoint.
3. Callback And Token Verification
The provider redirects to:
Paca requires the URL state and browser-bound state cookie to match, clears
the cookie on every callback exit, consumes the Valkey transaction with
single-use
GETDEL, exchanges the code with the PKCE verifier, and verifiesthe ID token's signature, issuer, audience, expiry, and nonce.
Provider Discovery, code exchange, JWKS retrieval, and UserInfo requests use a
dedicated HTTP client with a 10-second timeout.
4. Identity Resolution And Provisioning
The verified ID token's exact
(issuer, subject)resolves the Paca user. If nobinding exists, JIT provisioning atomically creates both the user and external
identity. A concurrent first-login unique-index race resolves the binding
created by the winning transaction instead of failing the other login.
5. Paca Session Issuance
OIDC and password login converge at
IssueSessionForUser. Paca reads thecurrent user and Global Role, issues its normal access and refresh token pair,
and writes the existing HttpOnly cookies. Session refresh re-reads the user's
current role so promotions and demotions apply on the next refresh and do not
survive refresh-token rotation as stale claims.
6. Logout
POST /api/v1/auth/logoutrevokes the Paca token family and clears Pacacookies. It does not end the provider's session, because doing so would affect
the user's other applications and requires provider-specific logout behavior.
Identity And Account Model
Durable Identity
user_external_identities ( id, user_id references users(id) on delete cascade, provider = 'oidc', issuer, subject, created_at, last_login_at, unique (issuer, subject) )The issuer is an identifier, not a URL to normalize. Paca trims surrounding
configuration whitespace but preserves the issuer value, including a trailing
slash, and requires exact agreement among configured issuer, Discovery
metadata, and the ID token
issclaim.Profile Attributes
preferred_username.email attributes.
submatches the ID token's subject.email_verified=true.sso-<first 12 hex characters of SHA-256(issuer NUL subject)>.the existing account.
SSO-Provisioned Accounts
JIT users receive an unknown random password hash and
password_login_enabled=false. Password login, password change,administrator reset, and password-set-token flows all reject these accounts.
This prevents an administrator reset from accidentally adding a second
authentication path to an SSO-only identity.
Authorization Model
USERGlobal Role.permission sets, so a configurable role name could point to a role carrying
elevated or wildcard permissions.
Two independent controls govern password login:
local_login_enabledis the installation-wide entry-point policy.users.password_login_enabledis the account-specific policy forSSO-provisioned users.
Both must allow password authentication before a password login can succeed.
SSO-Only Rollout And Lockout Prevention
A fresh Paca installation starts with a local-password administrator, while
JIT users always begin as
USER. SSO-only mode therefore requires a stagedrollout:
ADMINorSUPER_ADMIN;Paca rejects step 5 unless an active
ADMINorSUPER_ADMINhas an OIDCbinding for the exact currently configured issuer. A privileged binding from
an old or different provider does not satisfy the guard. Deleted users do not
satisfy it either.
Disabling OIDC always forces local login enabled. This prevents an
installation with no human login entry point.
Security Model
GETDELsingle-use transaction with 10-minute TTL(issuer, subject)submust match verified ID-tokensubUSERrole; no configurable default roleENCRYPTION_KEYReferrer-Policy: no-referrerand generic browser errorsServer logs may record the issuer, Paca user ID, acting administrator ID, and
high-level outcome. They must not record client secrets, authorization codes,
PKCE values, access tokens, ID tokens, or raw provider error bodies.
Current Deployment Configuration
The existing branch supports environment, Compose, and Helm configuration:
The redirect defaults to
<PUBLIC_URL>/api/v1/auth/oidc/callback. Issuer URLs must use HTTPS; loopback HTTP issuers are allowed for localdevelopment. Helm
stores the client secret in a Kubernetes Secret rather than embedding it in a
Deployment value.
Environment configuration remains backward compatible and is the effective
source on installations that have never saved SSO configuration through the
admin UI.
Proposed Admin Configuration
Permission Boundary
SSO configuration controls authentication for the whole installation and is
more sensitive than workspace branding. It must not reuse
settings.write,whose current contract only grants logo, favicon, name, and color changes.
Add
authentication.writeas a global permission. Grant it to the built-inADMINrole and, through wildcard permission,SUPER_ADMIN. Custom roles donot gain it automatically. Both reading the non-secret SSO configuration and
updating it require this permission.
Admin UI
The existing
/admin/settingspage gains permission-aware Branding andSingle sign-on tabs. The SSO tab contains:
Provider fields remain editable while OIDC is disabled, allowing an
administrator to prepare the configuration. A blank client-secret field means
"keep the currently configured secret". The API returns only whether a secret
is configured.
Save performs validation and, when enabled, OIDC Discovery. There is no
separate Test Connection action in the first version because Save already has
the same validation requirement. A successful save updates the login page
immediately. A failure keeps the form open and leaves the existing database and
runtime configuration unchanged.
When encrypted storage is unavailable, the page remains readable but saving
database-backed SSO configuration is disabled with a clear error. OIDC client
secrets never use the existing plaintext compatibility fallback used by some
older secret-bearing features.
Configuration Persistence And Precedence
Extend the singleton
workspace_settingsrow with:oidc_configured=falsemeans the installation has not adopted database-backedconfiguration and continues to use deployment configuration. The first
successful admin save sets it to true. From then on the database snapshot is
authoritative, including an intentional
oidc_enabled=false; stale deploymentvariables do not silently re-enable SSO.
The client secret is encrypted before persistence with the existing
AES-256-GCM encryptor. The encryption key remains deployment-owned and is
never stored in PostgreSQL. Losing or changing that key makes the stored secret
undecryptable and fails startup rather than silently disabling or weakening
authentication.
Admin API
Add two endpoints under the existing admin settings resource:
Example read response:
{ "data": { "source": "environment", "enabled": true, "issuer_url": "https://id.example.com/realms/company", "client_id": "paca", "client_secret_configured": true, "scopes": ["openid", "profile", "email"], "redirect_url": "https://paca.example.com/api/v1/auth/oidc/callback", "display_name": "Company SSO", "username_claim": "preferred_username", "local_login_enabled": true, "encrypted_secret_storage_available": true } }Example update request:
{ "enabled": true, "issuer_url": "https://id.example.com/realms/company", "client_id": "paca", "client_secret": "write-only replacement; omit to preserve", "scopes": ["openid", "profile", "email"], "redirect_url": "https://paca.example.com/api/v1/auth/oidc/callback", "display_name": "Company SSO", "username_claim": "preferred_username", "local_login_enabled": true }The response never contains the client secret or encrypted ciphertext.
Submitting an omitted or empty client secret preserves the effective existing
secret. When switching from environment to database configuration, the API may
encrypt and persist the already active environment secret if no replacement is
provided.
Runtime Reconfiguration
A small runtime manager owns one immutable active snapshot and an optional
OIDC service. It:
service.
OIDC routes are registered regardless of startup configuration. When OIDC is
disabled, the manager returns the existing generic SSO-unavailable error. The
public
/auth/configendpoint reads the active snapshot on every requestinstead of retaining startup-only booleans.
An admin update is serialized within the API process and follows this order:
No active state changes before persistence succeeds. The final activation is
an in-memory pointer swap with no expected failure path. Existing Paca browser
sessions are unaffected. A login transaction started against the prior
provider may fail after a configuration change and must be restarted; retaining
multiple provider generations is deliberately outside this version.
At API startup:
workspace_settingsafter PostgreSQL is available;oidc_configured=true, otherwise use thedeployment configuration;
The auth service consults the manager's local-login policy at request time, so
password login remains blocked below the HTTP presentation layer.
Multiple API Replicas
The Helm chart defaults to one API replica. This proposal guarantees immediate
activation only in the API process that handles the update. PostgreSQL stores
the new source of truth, but another already-running replica retains its prior
snapshot until restarted.
Deployments with multiple API replicas must perform a rolling restart after an
SSO change. Database notifications, Valkey pub/sub, polling, and snapshot
versioning are deferred until multi-replica live administration is a concrete
requirement.
Failure And Recovery Semantics
change.
server-side cause; keep the prior configuration.
network or storage work.
user retries.
fields; force local login on.
normal expiry; new OIDC logins fail. Local login remains the staged rollout
and break-glass path when enabled.
fail fast instead of starting with a different authentication policy.
Administrators should keep local login enabled until an SSO administrator is
verified. Database backup and protection of
ENCRYPTION_KEYare part of thesame recovery boundary; a database backup without its encryption key cannot
restore the OIDC client secret.
Compatibility And Rollout
not alter existing external identity bindings or users.
save.
when OIDC is disabled or the config request fails.
migrated.
Recommended rollout:
changes.
Testing Strategy
Existing OIDC Behavior
USERrole;Admin Configuration
authentication.writepermission and denial for branding-only roles;preserving the prior runtime snapshot;
/auth/configand OIDC route behavior;A local smoke test uses a loopback OIDC test provider, saves configuration
through the admin API, verifies
/api/v1/auth/config, and confirms the loginpage shows the configured SSO action without restarting the API.
Alternatives Considered
Keep Deployment-Only Configuration
This preserves the smallest backend but makes identity administration depend
on container, Helm, or host access. It does not provide the requested product
configuration experience.
Save In The UI But Require Restart
This avoids a runtime manager, but the UI would report saved configuration
that is not yet effective and would still require deployment access. The
single-process atomic snapshot is small enough to avoid that mismatch.
Distributed Live Configuration
PostgreSQL notifications, Valkey pub/sub, polling, or versioned snapshots could
update every API replica. The chart defaults to one API replica and there is no
demonstrated need for that permanent machinery yet, so this is deferred.
Automatic Email Linking
Email is mutable, recyclable, and not equivalent across issuers. Treating an
attribute match as proof of account ownership creates an account-takeover path.
Explicit administrator linking can be designed separately.
IdP-Driven Default Roles
Mapping arbitrary claims or a configurable default role directly to Paca RBAC
can grant elevated custom permissions to every new user. The first version
fixes JIT to built-in
USER; group-to-role mapping needs a separate policy,preview, and audit design.
Pass IdP Tokens Through As Paca Sessions
This would couple every Paca authorization path and machine credential to
provider token semantics. Issuing the existing Paca session keeps one
authorization model and contains OIDC at the human login boundary.
Discussion Questions
authentication.writethe right permission boundary, distinct frombranding-only
settings.write?product provide an explicit "return to deployment configuration" action?
is cross-replica live propagation required immediately?
first-version tradeoff?
an existing local account to adopt an external identity?
installation policy or a per-user choice?
All reactions