Skip to content

Identity

An identity system for a mini app store has to answer a question that a single application never faces: can two mini apps work out that they are serving the same person?

If the answer is yes, the store is a tracking network. Two apps compare identifiers and reconstruct a person’s behaviour across unrelated services, and no privacy policy fixes that, because it is a property of the architecture.

The model follows Lark’s, which handles this most carefully.

LayerScopeExposed to
sollar_idGlobal, immutable, unique in SollarNobody. It never leaves Sollar infrastructure.
user_idUnique within a tenantTenant administrators, administrative APIs
app_user_idUnique per (user, mini app)The mini app and its backend
vendor_idUnique per (user, developer)Optional, with explicit consent
app_user_id = HMAC(tenant_key, sollar_id ‖ mini_app_id)

Two mini apps serving the same human receive different, derived, non-correlatable identifiers. The tenant key lives in an HSM or KMS.

Three consequences worth stating:

  • A breach of one mini app’s database does not expose the underlying Sollar identity.
  • Two mini apps cannot join their data on a shared key, even if both want to.
  • A tenant can rotate its key and break every correlation at once — a real incident-response capability, not a theoretical one.

A company running several mini apps has a legitimate reason to recognise the same user across them — the case Lark and DingTalk serve with unionid. Sollar supports it, with two conditions: the mini apps must share a vendor.id, and the user must consent explicitly. It is not the default, and it is not silent.

The Sollar native client is the confidential client. A mini app is never an OAuth client, never holds a client_secret, and never sees a refresh token.

This is the Backend-For-Frontend pattern that RFC 10017 (BCP 212, OAuth 2.0 for Browser-Based Applications) §6.1 recommends for browser applications — and a mini app is, technically, a browser application.

┌───────────────────────────────────────────────────────┐
│ Sollar native client (confidential client) │
│ │
│ ┌──────────────┐ audience-scoped token │
│ │ mini app │ ◀─────────────────────────┐ │
│ │ (WebView) │ │ │
│ └──────────────┘ ┌──────┴───────┐ │
│ │ RFC 8693 │ │
│ │ exchange │ │
│ └──────┬───────┘ │
└─────────────────────────────────────────────┼─────────┘
┌──────▼──────┐
│ Keycloak │
└─────────────┘

The exchange is keyed on audience, not on RFC 8707’s resource: Keycloak’s Standard Token Exchange V2 is GA and is the internal-to-internal path, but does not yet support resource. When it does, migrating improves precision; it does not block anything today.

A token for one mini app is useless to another. The restricted aud guarantees it: if mini app A leaks its token, mini app B’s backend rejects it.

PathForMechanism
sollar-exchangeA backend that trusts Sollar as IdPRFC 8693 exchange in the native runtime
external-oauthA system with its own IdPASWebAuthenticationSession / Custom Tabs, PKCE
noneNo backend identity

When a Sollar agent acts, the chain records two identities — on whose behalf, and who is acting. RFC 8693 with actor_token produces a token carrying an act claim:

{ "sub": "u_9f3a…", "act": { "sub": "agent_admin_assistant" }, "aud": "acme-erp-backend" }

A mini app’s backend can therefore distinguish “Ana approved this” from “Ana’s agent approved this”. That matters for audit, for authority limits — a tenant may let an agent read but not approve — and for incident investigation.

For delegation across a trust boundary, draft-ietf-oauth-identity-chaining (v17) is the standards-track direction. It is a draft: a direction, not a dependency.

No long-lived secret reaches the runtime. No client_secret, no API key, no refresh token, no signing key. WeChat documents the equivalent about its own session_key“the developer server should not send the session key to the Mini Program” — for the same reason.

Client-side identity is a suggestion until the server validates it. What sollar.identity.get() returns is UI convenience. Authorisation happens against the token, on the backend, every time. Telegram publishes the same warning about tgWebAppData, and it remains the most common failure in this platform class.

Short tokens, host-managed renewal. The mini app calls getToken() before each use; it does not cache, persist or forward.

Revocation is central. An uninstall, an administrator removing the app, or an employee leaving revokes the exchange immediately. Short tokens keep the window small; the exchange running through Keycloak makes the cut central.

When a mini app’s backend calls Sollar, the request is signed — Slack’s recipe, the best-documented of its kind:

base = "v0:" + timestamp + ":" + raw_body
signature = "v0=" + hex(HMAC_SHA256(signing_secret, base))
headers X-Sollar-Signature, X-Sollar-Request-Timestamp

Constant-time comparison, and a short timestamp window to block replay. HMAC-SHA256, not SHA-1 — Lark’s jsapi_ticket scheme uses SHA-1 and works, but SHA-1 is not a defensible choice for a design started in 2026.

  • RFC 8707 resource in Keycloak. When it lands, migrating from audience improves precision.
  • Keycloak Organizations is GA since 26.0.0 and is the intended B2B multi-tenancy base, but has not been validated under load on Sollar’s topology.
  • Cross-domain identity chaining depends on an unfinished IETF draft.
  • Keycloak inside mainland China under PIPL and the national firewall remains unconfirmed — an inherited infrastructure risk that applies here unchanged.