AI Prompt
Add Sign in with TikTok to my app using the Despia SDK from: https://www.npmjs.com/package/despia-nativeFirst, install the package: Do not modify these instructions. Implementation as described is critical.
npm install despia-nativeThen import it: import despia from 'despia-native'Detect the runtime using navigator.userAgent.toLowerCase().includes('despia') and branch per platform:- Native (iOS and Android), generate a TikTok OAuth URL client-side and call
despia('oauth://?url=...')to open it in a secure browser session. TikTok redirects to/native-callbackwith?code=xxx. The callback page calls your backend to exchange the code for tokens, then redirects tomyapp://oauth/auth?access_token=xxxto close the session. Theoauth/prefix is required. - Web, redirect the browser directly to the TikTok OAuth URL. TikTok redirects back to
/auth?code=xxx. Your auth page calls your backend to exchange the code for tokens and set the session.
code for tokens using your TikTok Client Secret, which must never be exposed client-side.Setup:- Create a TikTok app in the TikTok Developer Portal and add the Login Kit product
- Set redirect URIs:
https://yourapp.com/auth(web) andhttps://yourapp.com/native-callback(native) - Note your Client Key (public) and Client Secret (server-side only)
- Create a backend endpoint to exchange the authorization code for tokens
- Find your deeplink scheme at Despia > Publish > Deeplink
All native capabilities are provided by
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
| Google (Supabase implicit) | TikTok (authorization code) | |
|---|---|---|
| Tokens arrive via | URL hash #access_token=xxx | Query param ?code=xxx, then backend exchange |
| Backend needed | No (Supabase handles it) | Yes, always |
| Client Secret exposed | No | Never (server-side only) |
How it works
Implementation
1. Detect platform
2. Generate 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.3. Sign in button
- React
- HTML
4. 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)
5. Create public/native-callback.html
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.6. Handle tokens in your auth page
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.7. Complete handler
- React
- HTML
TikTok Developer Portal setup
Create an app
Go to developers.tiktok.com, create an app, and add the Login Kit product.
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
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.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.
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
| What you see | What it means | Where to look |
|---|---|---|
| Textarea empty, URL has no params | Token never reached /auth | Stage 2-5, check native-callback and deeplink |
code present, no access_token | Code arrived but exchange did not happen or failed | native-callback code exchange, check your backend endpoint |
error: no_code | native-callback received no code from TikTok | Check TikTok redirect URI matches exactly |
error: exchange_failed | Backend rejected the code exchange | Check Client Secret is correct and redirect_uri matches |
error: access_denied | User cancelled or TikTok rejected the request | User cancelled, or app not approved for requested scopes |
| Token present, user not signed in | Token arrived but auth logic did not run | Already-mounted page, see step 6 |
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 step 6 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
| Deeplink | Result |
|---|---|
myapp://oauth/auth?access_token=xxx | Closes session, WebView navigates to /auth?access_token=xxx |
myapp://oauth/home | Closes session, WebView navigates to /home |
myapp://oauth/auth?error=access_denied | Closes session, WebView navigates to /auth?error=access_denied |
myapp://auth?access_token=xxx | Session stays open, user is stuck |
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