Skip to content

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-exchangeexternal-oauth
Use whenYour backend can trust Sollar as the identity providerYour system has its own IdP and will not delegate
The user seesNothing — it is silentA sign-in screen from your IdP, in the system browser
You receiveA short-lived, audience-restricted tokenA token from your own IdP
EffortOne manifest fieldAn OAuth client, plus PKCE

Default to sollar-exchange. Reach for external-oauth only when your system genuinely owns the identity.

manifest.json
"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/token
grant_type = urn:ietf:params:oauth:grant-type:token-exchange
subject_token = <the user's Sollar token>
audience = acme-erp-api
scope = orders.read orders.approve

Your 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.

your backend
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 only

A 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.

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.

manifest.json
"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 browser
const { 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_body
signature = "v0=" + hex(HMAC_SHA256(signing_secret, base))
X-Sollar-Signature: v0=…
X-Sollar-Request-Timestamp: 1756704000

Compare in constant time, and reject a timestamp outside a short window to block replay.

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.