Authentication
The Tax Kit is JWT-authenticated. The iframe never manages its own session — it asks the parent app for a fresh token every time it needs to authenticate, and the parent app gets that token from your backend.
The flow
- The iframe checks whether its current token expires within 30 seconds before each GraphQL request.
- If a fresh token is needed, it sends
REQUEST_ACCESS_TOKENto the parent app over post-robot. TaxKitProviderinvokes yourfetchAccessTokenfunction.- Your function calls your backend, which mints a JWT and returns it.
- The token is sent back to the iframe over post-robot. The iframe uses it for the request that triggered the rotation.
The iframe deduplicates token requests internally: if multiple in-flight GraphQL queries discover their token is expired at the same time, only one REQUEST_ACCESS_TOKEN event fires across the bridge. Your fetchAccessToken is invoked once per refresh window, not once per query.
What your backend has to do
Mint a short-lived CoinTracker JWT for the signed-in user and return it to your frontend over an authenticated channel.
JWT claim contract
The token is a standard JWT with CoinTracker-namespaced custom claims. Exact shape:
{
"https://cointracker.com/claims/partner_name": "<your-partner-slug>",
"https://cointracker.com/claims/partner_user_id": "<stable-user-id>",
"aud": "https://embedded.cointracker.com",
"iss": "<your-issuer>",
"exp": 1735689600
}
https://cointracker.com/claims/partner_namestringrequiredYour lowercase, immutable partner slug. Provided by CoinTracker during kickoff and registered server-side in CoinTracker's Partner enum. Cannot change after launch.
https://cointracker.com/claims/partner_user_idstringrequiredA stable identifier for the user across sessions in your system. CoinTracker uses this as the join key for the user's tax data — if this value changes for the same user, they'll appear as a new user to CoinTracker.
Choose carefully. A database primary key works well. An email address does not (changes when the user updates their email).
audstringrequiredAudience claim. Must be https://embedded.cointracker.com for the production environment. CoinTracker validates this against the iframe origin.
issstringrequiredYour issuer URL. The value you agreed on with CoinTracker during kickoff. CoinTracker uses this to locate your JWKS endpoint for signature verification.
expnumberrequiredStandard JWT expiry — Unix timestamp in seconds. See "Token rotation" below for recommended lifetime.
Signing
CoinTracker validates JWT signatures against a public key fetched from your JWKS endpoint (or against a key you provided directly during kickoff, depending on your signing approach).
- Algorithm and key approach are decided during kickoff. Most partners use RS256 with a JWKS endpoint they expose publicly.
- Key rotation: rotate by publishing the new key in your JWKS response before you start signing with it. CoinTracker caches JWKS responses — coordinate rotation timing with your integration owner.
The CoinTracker JWT must be minted server-side. Never expose your signing key in the browser. The frontend should only ever see the resulting opaque token.
What the frontend has to do
Implement a fetchAccessToken function that returns a fresh token from your backend:
const fetchAccessToken = async () => {
const res = await fetch('/api/cointracker-token', {
method: 'POST',
credentials: 'include', // forward your session cookie
});
if (!res.ok) {
return null; // signals unauthenticated to the iframe
}
const { access_token } = await res.json();
return access_token;
};
<TaxKitProvider fetchAccessToken={fetchAccessToken}>
<YourApp />
</TaxKitProvider>;
Return values
- A non-empty string — the iframe uses it as the bearer token.
nullorundefined— the iframe treats this as an unauthenticated state. Surface the appropriate "please sign in" UI from your end; the iframe will not attempt to recover.- An expired token — the iframe will request a fresh one within 30 seconds. Returning expired tokens repeatedly will degrade UX.
Errors
If fetchAccessToken throws, the iframe surfaces a TaxKitError.AuthenticationError. Handle this in your useTaxKit().errors array — typically by showing a re-authentication prompt and reloading the kit.
Token rotation
The iframe rotates tokens automatically:
- Tokens are refreshed when they're within 30 seconds of expiry.
- All in-flight iframe queries share the result of the active rotation — no thundering-herd of token requests.
- If your minted tokens are short-lived (under a minute), the rotation will fire frequently. Consider issuing longer-lived tokens (a few minutes) to reduce backend load.
Recommended lifetime: 5–15 minutes. Long enough that rotation is rare during a normal session, short enough that a leaked token has limited blast radius. Don't issue tokens with exp more than an hour out without coordinating with your CoinTracker contact.
Cookies and CORS
The iframe runs on a different origin from your parent app, so cookies are not shared. The provider does not rely on cookies for auth — only the JWT returned by fetchAccessToken.
Your /api/cointracker-token endpoint on your own domain can use whatever authentication mechanism your app already uses (session cookies, JWTs in headers, etc.) — that part stays on your origin.
OAuth-driven exchange import
If you're also a CoinTracker exchange integration and you want users to import their on-platform balances/transactions into the kit without re-entering API keys, the kit supports an OAuth-driven import flow.
Required endpoints on your side
- OAuth authorize endpoint — the URL the iframe redirects to when a user opts in. You agreed on this URL with CoinTracker during kickoff.
- OAuth token exchange endpoint — the standard OAuth 2.0 token endpoint that exchanges the auth code for an access token. CoinTracker calls this server-to-server.
Required redirect URL on CoinTracker's side
The callback URL you register with your OAuth authorization server is always:
https://embedded.cointracker.com/authorized/<your-partner-slug>
After the OAuth flow completes, CoinTracker redirects the user back into the iframe with the import result. The <your-partner-slug> segment is the same lowercase slug from your JWT's partner_name claim.
PKCE handling
By default CoinTracker performs PKCE on the iframe side. If your backend handles the auth-code exchange directly and already takes care of PKCE, coordinate with your integration owner on disabling CT's PKCE step.