Skip to main content
Pages

User Portal SSO

Standalone memory service for AI tools, with durable memory, evidence, team isolation, and MCP access.

User Portal SSO

This page is the source of truth for Dense-Mem browser SSO, OIDC provider configuration, team entitlements, SSO-owned API keys, and troubleshooting. For the surrounding portal surfaces, see Portals And API Keys. For request authentication and tenant enforcement, see Request Lifecycle And Security.

This page defines the target browser SSO contract. Provider-specific setup is version-sensitive; verify it against the deployed Dense-Mem release and the provider's current documentation.

This cookie-session contract applies to SSO login only. The user portal also continues to support Dense-Mem API-key authentication; an API-key portal login is not implicitly converted into an HttpOnly SSO session.

When SSO is configured, the user portal adds public SSO endpoints under the main Dense-Mem public base URL.

Register this redirect URI with the OIDC provider:

https://<dense-mem-host>/ui/api/sso/callback

This value is derived from SSO_PUBLIC_BASE_URL, so a deployment with:

SSO_PUBLIC_BASE_URL=https://dense-mem.example.com

uses:

https://dense-mem.example.com/ui/api/sso/callback

Dense-Mem exposes these user-portal SSO endpoints:

MethodPathPurpose
GET/ui/api/sso/providersLists enabled SSO providers for the login screen.
GET/ui/api/sso/start/:providerIdStarts OIDC login and redirects to the provider.
GET/ui/api/sso/callbackReceives the OIDC authorization code callback.
POST/ui/api/sso/teamSwitches the current SSO browser session to another entitled team.
POST/ui/api/sso/keyCreates the current SSO member's owned API key for the selected team.
POST/ui/api/sso/key/rotateRotates the current SSO member's owned API key for the selected team.
POST/ui/api/sso/logoutClears the local Dense-Mem SSO session cookies.
%%{init: {"sequence": {"rightAngles": true}}}%%
sequenceDiagram
  actor Browser
  participant DM as Dense-Mem /ui
  participant IdP as OIDC provider
  participant PG as PostgreSQL

  Browser->>DM: Start provider login
  DM->>IdP: Authorization request with state/PKCE
  IdP-->>DM: Authorization-code callback
  DM->>IdP: Validate tokens and load claims/groups
  DM->>PG: Match enabled mappings and team entitlements
  PG-->>DM: Teams, selected team, role, permission
  DM-->>Browser: Secure session + CSRF cookies
  Browser->>DM: State-changing /ui request + CSRF header
  opt Switch entitled team
    Browser->>DM: POST /ui/api/sso/team
    DM->>PG: Validate entitlement
  end

Do not use /ui/api/sso/logout as an OIDC post-logout redirect URI. It is a local API endpoint, not a browser landing page. The target contract does not perform RP-initiated OIDC logout with post_logout_redirect_uri unless a later version explicitly adds it.

SSO sessions and API keys are separate authentication sources. One SSO identity can be entitled to multiple teams, and the same provider group or role can map to many Dense-Mem teams. Dense-Mem creates one SSO team profile per entitled team for browser-session access, and /ui shows a team selector for those teams.

The SSO team profile has no API key material and is not rotated into an API key. It only represents the browser SSO session for the selected team.

SSO manager sessions see the Team tab and can manage same-team member profiles like a manager API-key login. Keys created from the Team tab are ordinary API keys.

SSO member sessions see the My key tab. Each SSO identity can own at most one active personal API key per team. That key is stored as a normal auth_source=api_key key with SSO ownership metadata so /ui can find the owner's key. Future bearer-token login with that key uses the standard API-key format rather than the browser session. When SSO authentication is configured, the auth middleware revalidates the SSO-owned key's current entitlement, so a revoked external entitlement can deny a still-present local key. It is not a hybrid cookie-and-bearer login.

The current SSO entitlement limits what the member can create or rotate from /ui:

SSO entitlement on selected teamMy key behavior
ManagerUse the Team tab instead of My key.
Member with read/write permissionCan create a read/write or read-only personal API key. Can rotate only if the existing personal key also has write scope.
Member with read-only permissionCan create a read-only personal API key. Cannot rotate it from /ui.

If the provider requires a post-logout redirect URI, use the portal URL:

https://<dense-mem-host>/ui

SSO Login Visibility

The user portal shows SSO login only when public SSO is ready:

  • SSO_PUBLIC_BASE_URL is an absolute http or https URL.
  • At least one SSO provider is enabled.
  • The provider has a valid issuer URL and client ID.
  • If the provider uses Client secret env, that environment variable exists and is not empty.

When these conditions are not met, /ui should only show API-key login and GET /ui/api/sso/providers should return no public providers. This is intentional for deployments such as the hosted demo.

Provider Fields

Configure providers in the control portal SSO section.

FieldMeaning
NameDisplay name shown on the user portal login screen.
KindUse OIDC for standards-based OIDC providers. Use provider-specific kinds only when Dense-Mem has a provider-specific integration.
Issuer URLProvider issuer base URL, without /.well-known/openid-configuration.
Client IDOIDC application client ID.
Client secret envEnvironment variable name that contains the client secret. Leave empty for public PKCE clients.
ScopesLogin scopes requested during the browser flow. Use at least openid, profile, and email.
Group claimsClaim names that contain group, role, or entitlement values.
Groups endpointOptional endpoint used when group claims are not present or when SSO entitlement refresh needs a provider lookup.
Groups scopesScopes for the groups endpoint. If a groups endpoint is configured, these are also requested during browser login.
EnabledEnables the provider for login after the rest of the setup is correct.

Group mappings connect provider group values to Dense-Mem teams:

Mapping fieldMeaning
TeamDense-Mem team to grant access to.
GroupExact group or role value from the provider claim.
PermissionDense-Mem API scopes for member mappings, such as read-only or read/write. Manager mappings always grant read/write.
RoleDense-Mem profile role, either member or manager. Manager role grants the /ui Team tab for that team.
StatusDisable a mapping without deleting it.

Generic OIDC Group And Role Mapping

Dense-Mem is not tied to a specific SSO vendor. Any OIDC provider can work when it can provide stable group, role, or entitlement values through one of these paths:

  • ID-token claims
  • UserInfo claims
  • a configured groups endpoint

Set Group claims to the claim names that contain the values you want to map. Dense-Mem accepts common claim shapes:

Claim shapeDense-Mem group values
["admin", "writer"]admin, writer
"admin"admin
{ "admin": {...}, "writer": {...} }admin, writer

The group mapping Group field must exactly match one emitted value. This can be an IdP group name, a role key, an application role, or any stable entitlement string your provider emits.

For claim-only providers, leave Groups endpoint and Groups scopes empty. Claim-derived entitlements are cached for the browser SSO session lifetime. For providers that do not emit usable group or role claims, configure Groups endpoint and the delegated or client-credential scopes it needs.

Examples:

Provider styleTypical Group claims
Generic OIDC group claimsgroups
Entra ID or Azure AD groupsgroups or provider-specific group endpoint
Application rolesroles
ZITADEL project rolesurn:zitadel:iam:org:project:<project_id>:roles, urn:zitadel:iam:org:project:roles

Use LOG_LEVEL=debug during setup and read id_token_claim_names, userinfo_claim_names, and groups in the SSO callback logs to confirm the right claim names and emitted values.

Example: ZITADEL Cloud With Project Roles

For hosted ZITADEL, use the ZITADEL instance that owns the application as the issuer URL:

https://<instance>.us1.zitadel.cloud

If you have multiple ZITADEL URLs, use the one whose /.well-known/openid-configuration issuer matches the application client ID.

In ZITADEL:

  1. Create or open the project used for Dense-Mem.

  2. Create a web application that uses Authorization Code with PKCE.

  3. Add the Dense-Mem redirect URI:

    https://<dense-mem-host>/ui/api/sso/callback
  4. Copy the application client ID.

  5. Create project roles for Dense-Mem access.

Example project role:

ZITADEL role fieldExample
Keydense-mem-admin
Display nameDense-Mem Admin
Groupdense-mem

Dense-Mem matches the role key from the token claim. The ZITADEL role group is only a ZITADEL-side organizer unless your emitted claim contains that value.

Assign the role to each user that should access Dense-Mem. For example, assign your own user the dense-mem-admin role before testing login.

In Dense-Mem, configure the provider:

Dense-Mem fieldValue
NameZITADEL
KindOIDC
Issuer URLhttps://<instance>.us1.zitadel.cloud
Client IDZITADEL application client ID
Client secret envEmpty for a public PKCE app, or an environment variable name for a confidential app secret.
Scopesopenid, profile, email
Group claimsurn:zitadel:iam:org:project:<project_id>:roles, urn:zitadel:iam:org:project:roles
Groups endpointEmpty when using ZITADEL role claims.
Groups scopesEmpty when using ZITADEL role claims.
EnabledChecked after the provider, roles, and mappings are ready.

If ZITADEL does not emit role claims, enable Assert Roles on Authentication on the project for UserInfo role claims, or User Roles Inside ID Token on the application for ID-token role claims. When required by your ZITADEL setup, add the project audience scope to Scopes:

urn:zitadel:iam:org:project:id:<project_id>:aud

Create a Dense-Mem group mapping whose Group value is the ZITADEL role key:

Dense-Mem mapping fieldExample
TeamMark
Groupdense-mem-admin
PermissionNot shown for Manager; manager implies read/write.
RoleManager
Statusenabled

With debug logging enabled, a successful claim read should show the ZITADEL role claim names and a group value matching the role key:

json
{
  "configured_group_claims": [
    "urn:zitadel:iam:org:project:<project_id>:roles",
    "urn:zitadel:iam:org:project:roles"
  ],
  "groups": ["dense-mem-admin"],
  "groups_from_userinfo": true
}

SSO Troubleshooting

Set LOG_LEVEL=debug while setting up SSO. The callback response now returns a safe setup hint, and the server log has provider and claim details.

Browser messageWhat to check
sso setup failed: no groups found in configured claimsGroup claims does not match the token or UserInfo claim names, or the provider is not emitting the expected group or role claims.
sso setup failed: group lookup failedThe groups endpoint, delegated scopes, or client credentials are wrong.
sso setup failed: no mapping matched the user groupsThe Dense-Mem mapping Group value does not exactly match the emitted role or group value.
sso setup failed: no enabled team entitlement matchedA mapping exists, but it is disabled or does not grant an enabled team entitlement.
sso access deniedThe provider is disabled, the session is invalid, or entitlement validation failed after setup.

For claim-based setups, the usual fix is:

  1. Read id_token_claim_names and userinfo_claim_names in the debug log.
  2. Put the provider's group or role claim names in Group claims.
  3. Set the Dense-Mem mapping Group to the emitted group or role value.
  4. Confirm the user is assigned that group, role, or entitlement in the IdP.

ZITADEL example references:

The canonical setting names and defaults remain in Configuration. This page owns the setup process and behavior; the configuration page owns the setting reference.