# Connect a backend

> Wire a mini app to your own ERP or CRM — either delegating identity to Sollar, or signing in against your own identity provider.

Source: https://miniapp.sollar.com/start/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

```json title="manifest.json"
"auth": {
  "type": "sollar-exchange",
  "audience": "acme-erp-api",
  "scopes": ["orders.read", "orders.approve"]
}
```

```ts
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:

```http
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.

```ts title="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`.

### 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

```json title="manifest.json"
"auth": {
  "type": "external-oauth",
  "issuer": "https://login.acme.example",
  "client_id": "acme-erp-miniapp",
  "scopes": ["openid", "profile", "orders"]
}
```

```ts
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.

> **DANGER**
RFC 8252 §8.12 forbids embedded user-agents in OAuth flows for native apps, and the reason is
concrete: inside a WebView the user cannot verify the URL or the certificate, and the host
application is technically capable of reading the typed password.

A third-party mini app asking for corporate credentials inside a WebView controlled by another third
party is precisely the attack the RFC describes. Sollar will not offer an API to do it, and no
customer request will change that — including "to make it feel more integrated".

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

## 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

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:

```json
{
  "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.
