@tassyir/app handles both, and this guide explains what it does so
you can rely on it.
Every one of them but the client id is a secret: keep it on your server, and
never log it or put it in a page, a URL or a repository.
Set up the SDK
Create oneTassyirApp per process. It needs your client id and secret, your
app’s numeric id (to verify webhooks), and a token store: where each
installation’s offline tokens live. A store must be durable (a set that
resolved survives a crash) and must return the latest set.
MemoryTokenStore exists for tests only: it forgets everything on restart.
The @tassyir/app README
has a complete server with a store and a refresh lock.
Who is looking: the ID token
Your page in the dashboard asks App Bridge for an ID token (Embedded pages) and sends it to your server, which verifies it. The ID token is the only proof of who is looking. The address your page is loaded at, its query string and App Bridge’s context are not: anyone can open your page’s URL with any query string. Read the user, the business and the installation from a verified ID token, nowhere else. An ID token is a JWT signed with Ed25519 (alg: "EdDSA"):
tassyir.verifyIdToken(token) checks it the way Tassyir does: alg: EdDSA
only, the key chosen by kid from
GET https://api.tassyir.io/apps/.well-known/jwks.json (cached an hour,
fetched again for an unknown kid), the signature before any claim, iss,
aud equal to your client id, and exp, nbf and iat with 30 seconds of
clock leeway. It returns userId, businessId, installationId,
sessionId and locale.
- Ask for a new ID token for every request to your server. It lives 60 seconds, and the token endpoint accepts each one once.
- Accept ID tokens over https only: one is a bearer credential for a minute.
- A page may ask for 30 ID tokens a minute. A page that calls your server more often should batch its requests, or your server can open its own short session once it has verified a token.
Embedded apps: one offline token per installation
tassyir.authenticate(idToken) verifies the ID token, then reuses the
installation’s stored offline token, or exchanges the ID token for one and
stores it. Your server can then call the Admin API as your app for that
installation, from a page request, a webhook or a background job.
Online tokens: acting for a person
An online token acts for the dashboard user who opened your page. What it can do is your app’s scopes and that person’s own permissions, in your installation’s stores and that person’s stores. An online token whose person may only see the orders assigned to them reads only those orders. Use one when your app must respect what the person looking may see.Headless apps: the install code
An app with no page (noadmin.page) and an auth.callback_url gets its
first tokens from an install code. Right after the install, the dashboard
sends the merchant’s browser to your callback with ?code=tsy_ic_…. The code
is valid 5 minutes, once, and only your client secret can redeem it.
Refresh safely
An access token lives one hour.tassyir.admin(installationId) refreshes it
when needed, and every refresh rotates the refresh token: the old one is
spent.
- A spent refresh token presented again within 60 seconds of its rotation is accepted (an answer lost on the way).
- Presented later, it revokes the whole family: every refresh and access token issued from it. The merchant then has to reopen your page (an embedded app) or reinstall your app (a headless app).
@tassyir/app’s token manager is built so this does not happen:
- At most one refresh runs per installation in a process; concurrent callers share its result.
- The rotated token set is written to your store before the new access
token is used. If the write fails, the call fails with
TOKEN_STORE_FAILED, and the set is written again on the next call. - A refresh whose answer was lost is retried with the same token for at most 40 seconds, inside the 60-second grace.
- A refresh token the process already rotated is never presented again
(
REFRESH_TOKEN_ALREADY_USED), even if a stale store hands it back.
refreshLock (an object with
withLock(key, task), built on a lock your processes share) to
createTassyirApp, and keep the tokens in a store every process reads. The
token set is read again inside the lock, so a process that waited uses the
fresh token instead of the spent one.
When a token stops working
The Admin API answers 401APP_TOKEN_INVALID for every token it will not
take: expired, revoked, or of an installation that is no longer active. The
answer never says which.
Forget an installation’s tokens with
tassyir.tokens.forget(installationId)
when you receive app/uninstalled or app/scopes_updated.
Client secrets
Create secrets in the console’s Credentials tab. A secret is shown once; Tassyir keeps only a hash and its first characters (tsy_cs_Ab3xY9Qz), so a
lost secret means a new one.
- Rotate by creating a new secret: every active secret keeps working for 24 more hours, so you can deploy the new one first (a secret already in its 24 hours keeps its end).
- An app has at most 3 live secrets, and a developer may create 10 an hour.
- Revoke a leaked secret in the console: the token endpoint refuses it at once. Access tokens already issued live out their hour; refreshing needs a live secret.
The token endpoint, without the SDK
POST https://api.tassyir.io/apps/oauth/token, with your client id and
secret as HTTP Basic authentication (never a client_secret parameter) and
form-encoded parameters (a flat JSON object works too):
refresh_tokenandrefresh_token_expires_in(90 days) come with offline tokens only;user_idwith online tokens only;issued_token_typewith an ID-token exchange only.scopeis the installation’s granted scopes, implied ones included. Thescopeparameter of the request is ignored.- Errors are OAuth’s
{ "error", "error_description" }, not problem+json:invalid_client(401: the client id or secret, or aclient_secretin the body),invalid_grant(an ID token, code or refresh token that is invalid, expired, used, revoked or another app’s),invalid_request,unauthorized_client(the app is suspended),unsupported_grant_type,slow_down(429, withRetry-After) andtemporarily_unavailable(503, withRetry-After). - Each app may make 300 token requests a minute.