Skip to main content

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

Loading diagram…
  1. The iframe checks whether its current token expires within 30 seconds before each GraphQL request.
  2. If a fresh token is needed, it sends REQUEST_ACCESS_TOKEN to the parent app over post-robot.
  3. TaxKitProvider invokes your fetchAccessToken function.
  4. Your function calls your backend, which mints a JWT and returns it.
  5. 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_namestringrequired

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

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

audstringrequired

Audience claim. Must be https://embedded.cointracker.com for the production environment. CoinTracker validates this against the iframe origin.

issstringrequired

Your issuer URL. The value you agreed on with CoinTracker during kickoff. CoinTracker uses this to locate your JWKS endpoint for signature verification.

expnumberrequired

Standard 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.
warning

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.
  • null or undefined — 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.