oauth://, web uses a standard redirect, and the code-to-token exchange always runs server-side because TikTok’s Client Secret cannot be exposed client-side.
All native capabilities here come from
despia-native. No additional native libraries are needed.Installation
- Bundle
- CDN
How TikTok OAuth differs from Google
TikTok uses the authorization code flow. This means tokens never arrive in the URL hash. Instead:- TikTok redirects to your callback with
?code=xxxas a query param - Your backend exchanges the code for tokens using your Client Secret
- Your backend returns the tokens to the callback page
- The callback page fires the deeplink with the tokens
How it works
Platform detection
Generating the TikTok OAuth URL
TikTok’s Client Key is public, it is visible in the OAuth URL the user sees. You can generate the OAuth URL client-side. The Client Secret is never needed here. Passdeeplink_scheme through the state parameter so your callback knows how to build the deeplink. TikTok echoes state back unchanged after auth.
Find your deeplink scheme at Despia > Publish > Deeplink. Replace
myapp throughout with your actual scheme.Sign in button
- React
- HTML
Backend, exchange the authorization code
Your backend receives thecode from the callback page, exchanges it with TikTok for tokens, optionally fetches the user profile, then returns the tokens to the client. The Client Secret is only ever used server-side.
- Custom Backend
- No-Code Platform (Supabase)
The native-callback.html bridge
This page runs inside the secure browser session. TikTok redirects here with?code=xxx. Unlike Google’s implicit flow, the code arrives as a query param, not a hash fragment, so React Router stripping the hash is not a concern here. A plain HTML file is still recommended to keep it simple and ensure it always runs fresh.
The code exchange happens inside this page via a
fetch call to your backend. The page stays open while the exchange happens, then fires the deeplink to close the session.The
oauth/ prefix in the deeplink is required. myapp://oauth/auth closes the browser session and navigates the WebView to /auth. myapp://auth without it does nothing and the user stays stuck in the browser.Handling tokens at /auth
After Despia closes the session and navigates to/auth?access_token=xxx, your auth page reads the token and sets the session. The web flow also lands here after exchanging the code.
If
/auth is already mounted when the deeplink arrives, your framework updates the URL without remounting. Token-reading logic that only runs on mount has already fired with empty params and will not run again. The fix is framework-specific and covered in the tabs below.- React
- Vue
- Vanilla JS SPA
- HTML
setSession() is a placeholder. Replace with your auth provider’s equivalent: supabase.auth.setSession() for Supabase, your own session management for a custom backend.Complete cross-platform handler
- React
- HTML
TikTok Developer Portal setup
1
Create an app
Go to developers.tiktok.com, create an app, and add the Login Kit product.
2
Configure redirect URIs
Under Login Kit settings, add both redirect URIs:
https://yourapp.com/authfor the web flowhttps://yourapp.com/native-callbackfor the native flow
3
Request scopes
Request at minimum
user.info.basic. This provides open_id, display_name, and avatar_url. Additional scopes (user.info.profile, user.info.stats) are optional.4
Note your credentials
Copy your Client Key (public, used client-side) and Client Secret (private, server-side only). Store the Client Secret in your backend environment variables only. Never expose it client-side.
5
Configure your deeplink scheme
Find your scheme at Despia > Publish > Deeplink and replace
myapp in your code with the actual value.Debugging
Use this section when the native OAuth flow is not working. Start by identifying which stage is broken:Debug overlay
Add this to your/auth page during development. Remove it before submitting to the App Store or Google Play.
- React
- HTML
Reading the output
Common failure points
Secure browser session does not open. Log the URL before passing todespia() and confirm it starts with https://www.tiktok.com/v2/auth/authorize/.
TikTok redirects to wrong URL. Both https://yourapp.com/auth and https://yourapp.com/native-callback must be registered in the TikTok Developer Portal under Login Kit. The redirect URI in your code must match exactly including the protocol and no trailing slash.
Code exchange fails. The redirect_uri sent to your backend must exactly match what was used in the original OAuth URL. A mismatch will cause TikTok to reject the exchange even if the code is valid.
Deeplink does not close the browser session. oauth/ must be present, myapp://oauth/auth. Without it Despia does not intercept the deeplink. Find your scheme at Despia > Publish > Deeplink.
Tokens arrive but sign-in never completes. The auth page was already mounted when the deeplink arrived. See Handling tokens at /auth for framework-specific fixes.
Supabase bad_jwt error. You are creating JWTs manually. Use generateLink() and verifyOtp() instead to generate real Supabase sessions.
Pre-submission checklist
TikTok Developer Portal
TikTok Developer Portal
- App created with Login Kit enabled
- Redirect URI
https://yourapp.com/authregistered - Redirect URI
https://yourapp.com/native-callbackregistered user.info.basicscope requested- Client Key and Client Secret noted
Native flow
Native flow
- OAuth URL generated client-side with correct Client Key
deeplink_schemepassed throughstateparam asuuid|schemenative-callbackpage calls backend to exchangecode, fires deeplink with tokens- Deeplink is
{scheme}://oauth/{path},oauth/prefix present - Deeplink scheme matches Despia > Publish > Deeplink
/authtoken handler re-runs on URL change, not only on initial mount
Backend
Backend
TIKTOK_CLIENT_SECRETstored in environment variables only, never client-side- Code exchange uses matching
redirect_uriin both the OAuth URL and the token request - For Supabase,
generateLink()andverifyOtp()used, not manual JWTs
Before submission
Before submission
- Debug overlay removed from
/authpage - Sign in tested on a physical device
- Sign in tested on both iOS and Android
- Error state tested, cancel the TikTok dialog and confirm the app handles it gracefully
Deeplink reference
FAQ
Why does TikTok need a backend but Google does not (with Supabase)?
Why does TikTok need a backend but Google does not (with Supabase)?
TikTok uses authorization code flow, which requires exchanging a short-lived code for tokens using your Client Secret. The Client Secret must never be exposed client-side. Supabase handles Google’s exchange internally, which is why the Google flow appears to not need a backend. For TikTok you always need a backend endpoint to do the exchange.
Why is the code exchanged in native-callback, not in /auth?
Why is the code exchanged in native-callback, not in /auth?
The native-callback page runs inside the secure browser session (Chrome Custom Tabs / ASWebAuthenticationSession). Doing the exchange here means the tokens are ready before the deeplink fires. If you waited until
/auth, you would need to carry the raw code through the deeplink instead of the tokens, which is possible but adds an extra round trip after the browser closes.What does the oauth/ prefix do?
What does the oauth/ prefix do?
It signals Despia to close the secure browser session and navigate the WebView to the path that follows.
myapp://oauth/auth closes the session and opens /auth. Without oauth/ the deeplink is ignored and the user stays in the browser.Why pass deeplink_scheme through state?
Why pass deeplink_scheme through state?
Once TikTok opens the secure browser session, your native-callback page has no direct access to the original Despia context. The
state parameter is the only value TikTok echoes back unchanged, making it the correct carrier for anything your callback needs to know about the original request.Tokens are in the URL but the user is not signed in.
Tokens are in the URL but the user is not signed in.
The
/auth page was already open when the deeplink arrived. Your framework updated the URL without reloading.Fix per framework:- React, add
searchParamsto youruseEffectdependency array - Vue, use
watch: { '$route.query': { immediate: true, handler } }instead ofmounted() - Vanilla JS / HTML, call your handler on load and add
window.addEventListener('popstate', handler)
Resources
NPM Package
despia-native
OAuth Reference
Generic OAuth protocol docs
TikTok Developer Portal
Configure your TikTok OAuth app