Connect a backend
There are two ways to authenticate, and choosing correctly matters more than anything else in this page. Pick based on who owns the user’s identity for your system.
sollar-exchange | external-oauth | |
|---|---|---|
| Use when | Your backend can trust Sollar as the identity provider | Your system has its own IdP and will not delegate |
| The user sees | Nothing — it is silent | A sign-in screen from your IdP, in the system browser |
| You receive | A short-lived, audience-restricted token | A token from your own IdP |
| Effort | One manifest field | An OAuth client, plus PKCE |
Default to sollar-exchange. Reach for external-oauth only when your system genuinely owns the
identity.
Delegating identity to Sollar
Section titled “Delegating identity to Sollar”"auth": { "type": "sollar-exchange", "audience": "acme-erp-api", "scopes": ["orders.read", "orders.approve"]}const { token } = await sollar.auth.getToken()await fetch('https://erp.acme.example/approvals', { headers: { Authorization: `Bearer ${token}` },})Behind that, the native Sollar client — which is the confidential OAuth client, not your mini app — performs an RFC 8693 token exchange against Keycloak:
POST /realms/{tenant}/protocol/openid-connect/tokengrant_type = urn:ietf:params:oauth:grant-type:token-exchangesubject_token = <the user's Sollar token>audience = acme-erp-apiscope = orders.read orders.approveYour backend then validates the result the way it would validate any JWT: signature against the
tenant’s JWKS, aud equal to your audience, exp in the future, iss the expected realm.
const claims = await verifyJwt(bearer, { jwks: `https://auth.sollar.com/realms/${tenantId}/protocol/openid-connect/certs`, audience: 'acme-erp-api',})const userId = claims.sub // the app_user_id — stable for YOUR mini app onlyA token minted for your mini app is useless in another. The audience restriction is what
guarantees it: if your token leaks, the next mini app’s backend rejects it on aud.
Mapping Sollar users to your users
Section titled “Mapping Sollar users to your users”claims.sub is the app_user_id — derived per (user, mini app), so two mini apps serving the same
person receive different, non-correlatable identifiers. This is intentional: it is what stops a mini
app store from becoming a tracking network.
For your first sign-in you therefore need a linking step. Ask for sollar.identity.basic, use the
email or employee ID your tenant has configured, and store the mapping on your side. Afterwards,
app_user_id is your stable key.
Signing in against your own IdP
Section titled “Signing in against your own IdP”"auth": { "type": "external-oauth", "issuer": "https://login.acme.example", "client_id": "acme-erp-miniapp", "scopes": ["openid", "profile", "orders"]}await sollar.auth.signIn() // opens the system browserconst { token } = await sollar.auth.getToken()signIn() opens ASWebAuthenticationSession on iOS or Custom Tabs on Android. The user sees a real
browser, with a real address bar and a real lock icon. The host exchanges the code with PKCE and
stores the result in the OS keychain; your mini app never sees the refresh token.
Sending a request from your backend to Sollar
Section titled “Sending a request from your backend to Sollar”When your backend calls Sollar — to post a card, say — sign the request:
base = "v0:" + timestamp + ":" + raw_bodysignature = "v0=" + hex(HMAC_SHA256(signing_secret, base))
X-Sollar-Signature: v0=…X-Sollar-Request-Timestamp: 1756704000Compare in constant time, and reject a timestamp outside a short window to block replay.
Rules that do not bend
Section titled “Rules that do not bend”No long-lived secret reaches the mini app. No client_secret, no API key, no refresh token, no
signing key. If your design needs one in the browser, the design is wrong. WeChat documents the same
rule about its own session_key, and for the same reason.
Client-side identity is a suggestion until your server validates it. Always.
Call getToken() per use. Do not cache it, do not persist it, do not pass it on.
Network hosts live in the manifest. erp.acme.example must be in network.connect or the
request is blocked by native code before it leaves the device — regardless of what your CSP says.
When an agent calls on the user’s behalf
Section titled “When an agent calls on the user’s behalf”If a Sollar AI agent invokes one of your actions, the token carries two identities: the user it
acts for, and the agent acting. RFC 8693 actor_token produces an act claim:
{ "sub": "app_user_9f3c…", "act": { "sub": "agent:workforce/approvals-assistant" }}Your backend can distinguish “Ana approved this” from “Ana’s agent approved this” — which matters for
audit, for spending limits, and for incident investigation. Read the act claim and record it.