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.
Tokens are issued by CoinTracker's Auth0 tenant, not by your own signing keys. Your backend obtains a token on behalf of the signed-in user using credentials CoinTracker provisions during kickoff. The exact token-issuance flow (Auth0 M2M, Auth0 social/database connection, or another arrangement) is decided per partner — coordinate with your integration owner.
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 obtains a token from CoinTracker's Auth0 tenant 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 the token must contain
The embedded-api Worker validates every incoming token against CoinTracker's Auth0 JWKS endpoint and checks the following claims. Your token-issuance flow must produce tokens that satisfy them:
{
"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": "https://auth.cointracker.com/",
"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 exactly https://embedded.cointracker.com. The embedded-api Worker rejects tokens with any other audience.
issstringrequiredIssuer claim. Must match CoinTracker's Auth0 tenant for the environment you're targeting — https://auth.cointracker.com/ in production, https://auth-staging.cointracker.com/ in staging. (Trailing slash is required.) CoinTracker fetches signing keys from this issuer's /.well-known/jwks.json endpoint.
expnumberrequiredStandard JWT expiry — Unix timestamp in seconds. See "Token rotation" below for recommended lifetime.
Signing
CoinTracker validates signatures against the JWKS endpoint of its own Auth0 tenant. You do not host a JWKS endpoint and you do not sign tokens with your own private key — the Auth0 tenant signs them. Your backend's job is to obtain an Auth0-issued token for the right user with the right custom claims.
The token-issuance pattern (Auth0 Machine-to-Machine, Resource Owner Password, a custom Action that injects the custom claims, etc.) is set up during kickoff with your integration owner.
Even though the Auth0 tenant signs the token, your client secret or whatever Auth0 credential you've been issued must stay server-side. The frontend should only ever see the resulting opaque token returned from fetchAccessToken.
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.