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.
Four layers
Section titled “Four layers”The model follows Lark’s, which handles this most carefully.
| Layer | Scope | Exposed to |
|---|---|---|
sollar_id | Global, immutable, unique in Sollar | Nobody. It never leaves Sollar infrastructure. |
user_id | Unique within a tenant | Tenant administrators, administrative APIs |
app_user_id | Unique per (user, mini app) | The mini app and its backend |
vendor_id | Unique per (user, developer) | Optional, with explicit consent |
app_user_id is the load-bearing one
Section titled “app_user_id is the load-bearing one”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.
vendor_id is the deliberate exception
Section titled “vendor_id is the deliberate exception”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 host is the OAuth client
Section titled “The host is the OAuth client”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.
Three paths
Section titled “Three paths”| Path | For | Mechanism |
|---|---|---|
sollar-exchange | A backend that trusts Sollar as IdP | RFC 8693 exchange in the native runtime |
external-oauth | A system with its own IdP | ASWebAuthenticationSession / Custom Tabs, PKCE |
none | No backend identity | — |
Agent delegation
Section titled “Agent delegation”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.
Server callback authentication
Section titled “Server callback authentication”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_bodysignature = "v0=" + hex(HMAC_SHA256(signing_secret, base))headers X-Sollar-Signature, X-Sollar-Request-TimestampConstant-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.
Open questions
Section titled “Open questions”- RFC 8707
resourcein Keycloak. When it lands, migrating fromaudienceimproves 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.