If you’re distributing an integration to many operators through code that runs outside infrastructure you control — for example a WordPress plugin, browser extension, client-side app extension, or packaged on-premise tool — you cannot embed OAuth 2.0 client credentials in that distributed code. This guide compares two patterns: the recommended broker pattern, and a lighter authorization proxy alternative for cases where you accept that Stora tokens will live in each plugin installation.Documentation Index
Fetch the complete documentation index at: https://docs.stora.co/llms.txt
Use this file to discover all available pages before exploring further.
Choose an option
Use the broker pattern when:- Your integration is installed by each operator on infrastructure you don’t control (their WordPress site, browser, server, or laptop).
- You need access to operator data beyond simple read-only, public information.
- You want a single place to rotate credentials, revoke individual installations, and monitor usage.
- You need to keep the Stora
client_secretout of distributed plugin code, but you cannot operate a full broker that stores tokens and proxies every API call. - Your plugin can protect Stora access and refresh tokens on each operator’s infrastructure.
- You accept weaker revocation, observability, and compromise isolation than the broker pattern provides.
| Pattern | Stores Stora client_secret | Stores Stora access and refresh tokens | Responsible for token security |
|---|---|---|---|
| Broker | Your broker | Your broker | Your backend infrastructure |
| Authorization proxy | Your proxy | Each plugin installation | Your plugin and the operator’s infrastructure |
A hosted Shopify app usually uses the standard Authorization Code flow because the app backend is operated by the app developer and can act as the confidential OAuth client. Do not put Stora credentials or tokens in Shopify theme code, app extensions, or other client-side/distributed code. Route those calls through a backend you host.
Option 1: Broker pattern (recommended)
The broker pattern is the preferred option for public plugins. It keeps Stora tokens and OAuth credentials on infrastructure you control, and gives you one place to revoke installations, monitor usage, and handle refresh-token rotation.Architecture
The broker pattern splits responsibility across three actors. Your distributed integration code runs inside an environment you don’t control — it holds no Stora credentials. Your broker is a backend service you host — it holds your Storaclient_id and client_secret, stores each operator’s access and refresh tokens, and is the only thing Stora sees on the other end of OAuth. Stora issues exactly one confidential OAuth application to you, regardless of how many installations of your integration exist.
Connection flow at a glance
API call flow at a glance
Why the broker doesn’t hand Stora tokens to the plugin
It’s tempting to skip the broker’s own token layer and just forward Stora’saccess_token and refresh_token to the plugin. Doing so re-creates the problems the broker exists to avoid:
- Refresh-token theft persists. Stora’s refresh tokens are long-lived bearer credentials. Once exfiltrated from a plugin’s database or backup, they work until revoked.
- You lose the kill switch. To cut off a single installation you’d have to revoke Stora tokens — which affects the operator’s other integrations and requires reconnection.
- You lose scope narrowing. Broker-issued tokens can be narrower than the upstream OAuth grant. Raw Stora tokens cannot.
- You lose observability. Direct plugin → Stora calls bypass your broker’s logs, metrics, and rate limiting.
- Refresh rotation gets messy. Stora rotates refresh tokens on every use; with plugin-held tokens, every rotation has to be pushed back to each installation.
- The
client_secretis irrelevant after exfiltration. Attackers use stolen tokens directly — they don’t need to mint new ones.
client_secret in the plugin. Encrypt tokens at rest and be clear-eyed about what encryption buys you. See Alternatives considered for the full list of mitigations and their limits — the short version is that encryption at rest only meaningfully protects against DB dumps, not against RCE or a malicious sibling plugin.
Connecting an operator
Before you can make API calls on behalf of an operator, the operator’s installation needs to go through the Authorization Code flow once. All three ofclient_id, client_secret, and the redirect_uri belong to your broker — the plugin never sees them. The flow below shows what your broker implements.
This guide assumes Stora has already issued you a confidential OAuth 2.0 application with a single
redirect_uri pointing at your broker (e.g. https://broker.yourcompany.com/stora/callback). Partner credentials are not self-serve today — we provision them for you during onboarding. See Building a partner integration for how to get set up.Plugin starts the connection
The operator clicks “Connect to Stora” in your plugin’s UI. The plugin redirects the operator’s browser to your broker’s The
/connect endpoint, passing whatever you use to identify this installation (site URL, install ID, tenant slug).plugin_nonce is yours to design — it lets the broker trust that this redirect actually came from a real installation of your plugin, not a random browser. See What your broker is responsible for for plugin-to-broker authentication notes.Broker redirects to Stora
The broker mints a fresh The
state value, stores {state → install_id} in short-lived storage (Redis, or a DB row with a TTL — a few minutes is enough), and redirects the operator’s browser to Stora:state parameter is not optional for a broker: it prevents CSRF on the callback and lets you correlate the returning code with the right installation.Operator approves in Stora
The operator logs in to their Stora BackOffice (if not already) and approves the requested scopes. Stora redirects back to your broker’s callback with a one-time code:
Broker exchanges the code for tokens
Verify the Response:
state matches one you issued recently and recover the install_id. Then exchange the code at Stora’s token endpoint, authenticating with your client_secret:Broker stores tokens and returns to the plugin
Store the From this point on, the plugin holds only the broker token. Stora’s
access_token, refresh_token, and expires_at in your broker’s database, keyed by install_id. Issue a broker-scoped token (an opaque identifier you mint yourself) back to the plugin and redirect to the plugin’s success URL:access_token and refresh_token live on your broker and never leave it.Making API calls
Once connected, the plugin makes requests to your broker, the broker translates them into Stora API calls using the storedaccess_token, and returns the response to the plugin. The broker is responsible for refreshing expired tokens silently and for surfacing a “reconnect required” signal when refresh fails.
The happy path
The plugin calls your broker. Authenticate the plugin with the broker-scoped token you issued during the connect flow:expires_at, refreshes if needed (see below), and proxies the request to Stora:
Refreshing tokens
Stora access tokens expire after 2 hours. When the storedexpires_at is within a small buffer of now (e.g. 5 minutes), refresh before making the call:
access_token and a new refresh_token. The previous refresh token is revoked immediately. Your broker must atomically update both values, or the next refresh will fail.
When refresh fails
A400 from the Stora token endpoint with "error": "invalid_grant" means the grant is no longer valid — usually because the operator disconnected your integration in Stora BackOffice. You cannot recover this without the operator going through the connect flow again.
The broker should: mark the installation’s tokens as revoked locally, return a well-defined error to the plugin (e.g. 401 Unauthorized with a body like {"error":"stora_reconnect_required"}), and let the plugin prompt the operator to reconnect.
Rate limits
Stora applies rate limits per operator (10 req/s, 60 req/min — see rate limiting). Your broker adds no rate-limit protection by default; a429 from Stora propagates back to the plugin. Consider short request coalescing (same plugin, same endpoint, same operator, within the same second) if your plugin is chatty.
Disconnecting and reconnecting
Operators need to offboard cleanly, re-grant access after revocation, and occasionally switch which Stora account they’ve connected — without uninstalling the plugin. Your plugin’s settings UI must expose Disconnect and Reconnect actions, and the broker must back them with the logic below.Disconnect
The plugin’s settings UI exposes a Disconnect button. When the operator clicks it, the plugin calls a disconnect endpoint on your broker — authenticated with the broker token — and your broker:- Calls Stora’s
POST /oauth2/revokewith the stored access token and your OAuth client credentials. - Deletes the stored Stora tokens and the broker token for this installation.
- Returns
204 No Contentto the plugin.
Reconnect
The plugin’s settings UI also exposes a Reconnect button, which redirects the operator back through your existing connect entry point. The broker overwrites the installation’s stored tokens on the next successful callback — no new endpoint required. Expose Reconnect even while the current tokens are still valid. Operators use it to re-grant expanded scopes when your integration adds a new feature, or to switch to a different Stora account without a reinstall.On plugin uninstall
The integration should fire the same disconnect flow automatically when the operator removes it from their host, using whatever uninstall or deprovisioning hook the platform exposes — for example WordPress’sregister_uninstall_hook, or Shopify’s app/uninstalled webhook for hosted Shopify apps.
Treat uninstall hooks as a hygiene layer, not a replacement for the explicit Disconnect action. Some platforms don’t fire them reliably: a user who deletes a WordPress site wholesale never triggers plugin uninstall hooks, and webhook delivery can fail on any hosted platform. The explicit Disconnect action plus Stora-side revocation remains the source of truth.
What your broker is responsible for
The broker is a piece of infrastructure you own and operate. At minimum, plan for the following.Plugin-to-broker authentication
Stora authenticates your broker viaclient_secret. Your broker needs its own way to authenticate plugin installations. A common pattern: the plugin generates a long random value at activation time, registers it with the broker (HTTPS call keyed by install URL + admin email), and uses it as a bearer token on subsequent calls. Bind each token to one installation so a token stolen from one site can’t be used to impersonate another. HMAC-signing requests with a per-install secret is a stronger alternative.
Token storage and rotation
Storeaccess_token, refresh_token, and expires_at per installation. Refresh tokens rotate on every use — the previous one is revoked the instant Stora returns a new one. Write the new refresh_token to your database before you return the response to whoever triggered the refresh, and serialise refresh attempts per installation so two parallel workers can’t race. Encrypt tokens at rest; the column on your broker’s database is as sensitive as your Stora client_secret.
Per-installation kill switch
Because the broker mints its own plugin-facing tokens, you can cut off a single installation without touching Stora: invalidate the broker token locally and the plugin starts receiving your reconnect error on every call. This is the fastest response to a compromised installation — no Stora support ticket, no scope re-negotiation, no impact on other installations.Monitoring and abuse detection
Log every call that passes through the broker withinstall_id, endpoint, and status code. Basic alerts to set up: sustained 429 rates per installation (indicates a runaway plugin), sustained 4xx rates (indicates a broken installation or probing), sudden growth in call volume from a single installation, and anything that looks like credential-stuffing against your own /connect endpoint. Ship these to whatever you already use — you don’t need anything Stora-specific.
Disconnect and reconnect endpoints
Expose routes the plugin can call to trigger Disconnect (broker revokes tokens at Stora and wipes local state) and Reconnect (broker redirects back through the connect flow). Wire the same revoke-and-discard path into the plugin’s platform-specific uninstall hook as best-effort hygiene.Option 2: Authorization proxy (lighter alternative)
The authorization proxy is a lighter alternative to the broker pattern. It keeps your Storaclient_secret on infrastructure you control, but it does not keep Stora tokens out of the plugin. The plugin installation — for example a WordPress site — stores the operator’s Stora access_token and refresh_token, and calls the proxy whenever it needs to exchange an authorization code or refresh a token.
How the authorization proxy works
The plugin starts the OAuth connection by sending the operator to the authorization proxy. The proxy validates that the plugin callback URL is HTTPS and allowed for the installation, creates a signedstate value containing the callback context, and redirects the operator to Stora. After approval, Stora redirects back to the proxy with the authorization code; the proxy verifies the signed state, creates a signed short-lived code_context bound to that code and installation, and redirects the browser back to the plugin with the code.
The plugin then calls the proxy server-to-server to exchange the code for Stora access and refresh tokens. The server-to-server call must authenticate the plugin installation, for example with an Authorization header or HMAC signature. The plugin stores the returned tokens locally, but never calls Stora’s token endpoint directly. When the access token expires, the plugin sends the stored refresh token to the proxy; the proxy authenticates to Stora with the partner client_secret, performs the refresh, and returns the rotated tokens for the plugin to replace locally.
The code_context is not a replacement for the Stora authorization code. It is a short-lived signed value created by the proxy so the proxy can stay stateless. It binds the authorization code to the plugin installation and callback that started the flow. When the plugin later calls /token, the proxy verifies the code_context before using its client_secret to exchange the code with Stora.
You can implement code_context as a signed JWT/JWS or any equivalent authenticated token format. The payload should include the authorization code hash, installation identifier, callback URL, and short expiry. Sign it with a proxy-only secret. Do not put the Stora client_secret or access/refresh tokens in code_context.
/token is called, verify the signature, expiry, install_id, callback URL, and that sha256(code) matches code_hash before exchanging the code with Stora.
The diagram below uses WordPress as the example plugin host.
Authorization proxy trade-offs
Compared with the broker pattern, the authorization proxy removes the need to store per-installation Stora tokens on your backend. That makes the proxy easier to operate, and it can be close to stateless if you use signed, expiringstate and code_context values.
The trade-off is that Stora tokens now live in the plugin installation. A compromised WordPress database, backup, admin account, server, or sibling plugin can expose the operator’s Stora tokens. You also lose the broker’s per-installation kill switch, API-level observability, rate limiting, and scope narrowing. Use this pattern only when that risk is acceptable, and document the token-storage responsibility clearly for operators.
Alternatives considered
You don’t need to read this section to build the integration. It exists for partners who want to understand why we recommend the broker pattern over the alternatives the OAuth 2.0 spec technically allows.Why not a public client with PKCE?
Why not a public client with PKCE?
PKCE protects authorisation codes from interception during the redirect. It does not protect OAuth tokens after they’re issued. For a plugin distributed to many operators, the consequences are:
- Tokens live on the operator’s infrastructure.
wp_optionsis plaintext; other sensitive values in the same table get exfiltrated together in DB dumps, backups copied to staging, and compromised-plugin incidents. - No client authentication. A public
client_idcan be copied into any application. PKCE binds a code to a device but proves nothing about who the client is. A phishing app reusing yourclient_idpresents the real Stora consent screen with your branding. - Refresh tokens are long-lived bearer credentials. Once out of the operator’s database they work until revoked. Rotation shrinks the window; it doesn’t close it.
- Loose redirect URIs. Thousands of installations mean either wildcard redirect URIs (vulnerable to subdomain takeover) or Dynamic Client Registration (see below).
- No abuse isolation. Revoking the public
client_idbreaks every installation simultaneously. - No consent phishing protection. An attacker can complete the flow with your
client_idsince there’s no secret to prove identity.
Dynamic Client Registration (RFC 7591)
Dynamic Client Registration (RFC 7591)
DCR would let each plugin installation register its own
client_id (still public, still PKCE) at install time. Each site then has its own OAuth client record on Stora’s side, which solves the redirect-URI and abuse-isolation problems.Stora does not currently support Dynamic Client Registration. If you have a use case that specifically requires it, get in touch — we prioritise based on partner demand. DCR alone doesn’t solve the token-storage problem (tokens still live in wp_options); it mainly simplifies the consent and redirect-URI side of operating many public-client installations.If you insist on sharing Stora tokens with the plugin
If you insist on sharing Stora tokens with the plugin
We understand the broker pattern adds operational overhead. If you decide to let the plugin store Stora access and refresh tokens, use the authorization proxy shape above. Do not put the Stora
client_secret in the plugin, and do not have the plugin call Stora’s token endpoint directly. The plugin should receive the authorization code in its callback, then call your proxy to exchange the code or refresh token. Your proxy authenticates to Stora with the client_secret and returns the resulting tokens to the plugin.At minimum, do the following:- Keep the
client_secretproxy-side. The proxy exists to protect the confidential OAuth client credentials. The plugin should only ever see the authorization code,code_context, access token, refresh token, expiry, and the plugin-to-proxy credential you design. - Use signed, expiring
stateandcode_context. If you want the proxy to stay stateless, encode the callback URL, installation identifier, and nonce inside a signedstatevalue. Validate the callback URL is HTTPS and allowlisted for the installation before redirecting. After Stora redirects back, issue a signedcode_contextbound to the authorization code hash, installation identifier, and short expiry. Reject expired, tampered, or mismatched values. - Authenticate plugin-to-proxy calls. The
/tokenand/refreshendpoints still need plugin authentication, such as anAuthorizationheader with an install-time shared secret or HMAC-signed requests. Do not pass that secret through browser redirects or query strings. Otherwise anyone with a code or refresh token can ask your proxy to use yourclient_secretfor them. - Encrypt tokens at rest. WordPress core stores
wp_optionsvalues as plaintext. Google Site Kit’sData_Encryptionis the de-facto reference implementation — AES-256-CTR with a key derived fromLOGGED_IN_KEYinwp-config.php. Most major plugins (Jetpack, WooCommerce Stripe, Mailchimp for WooCommerce) don’t encrypt at all; doing so puts you ahead of the ecosystem default. - Understand what encryption buys you. It only mitigates stolen SQL backups, misconfigured phpMyAdmin, read-only DB leaks, and compromised noisy neighbours on shared MySQL. It does not mitigate a compromised WordPress admin, an RCE on the host, a malicious sibling plugin, or a
wp-config.phpleak — in all those cases the key is on the same box. - Narrow your scopes aggressively. Request only what the plugin genuinely needs. Every scope you request is a scope an attacker inherits.
- Rotate refresh tokens through the proxy and persist the replacement. Stora rotates refresh tokens on every successful refresh. The plugin must send the current refresh token to the proxy, the proxy must refresh with the
client_secret, and the plugin must atomically replace both the access token and refresh token it has stored. - Accept that you cannot revoke one installation without revoking the operator’s entire OAuth grant. Without a broker-issued token layer, there is no local kill switch granularity — you either revoke the Stora grant or you don’t.
client_secret; it does not remove the plugin host from the Stora token trust boundary. The broker pattern does, which is why we recommend it for anything touching customer PII, payments, contracts, access control, or other sensitive data.Next steps
You now have the OAuth side built. Before going live, also review:Partner programme requirements
Timeline Events, idempotency, error handling, scope principles.
Rate limiting and backoff
Per-operator limits apply. Your broker or plugin must handle 429s, depending
on the pattern.
Webhooks
React to Stora events instead of polling. Your broker or proxy receives them
on a single URL.
Authentication reference
Full details on OAuth flows, token exchange, and token refresh.