TikTok Login Kit: OAuth flow and token lifecycle
TikTok Login Kit is OAuth 2.0. Your app sends the user to https://www.tiktok.com/v2/auth/authorize/ with its client_key, scopes and a state value, receives a code on the registered redirect URI and exchanges it server-side at /v2/oauth/token/. The access token lasts 24 hours and the refresh token 365 days, so schedule a daily refresh.
Updated:
How does TikTok OAuth work?
TikTok uses the authorization code flow. The user approves the requested scopes on TikTok's authorization page, TikTok redirects back with a code, and your server exchanges it — together with the client key and client secret — for an access token, a refresh token and the user's open_id. API calls then send the access token as Authorization: Bearer.
Login Kit exists for Web, Desktop, iOS and Android. The web flow relies on a server that holds the client secret and checks the state value; desktop and mobile flows add PKCE and send a code_verifier during the code exchange.
Scopes decide what the token can do. user.info.basic is added automatically; anything else, such as video.list or video.publish, must be added to your app in the developer portal and covered in app review.
Key terms: client key, client secret, open_id and tokens
- client_key
- The app's public identifier. Sent in the authorization URL and in token requests.
- client_secret
- The app's confidential credential. Used only in server-side requests to
/v2/oauth/token/and/v2/oauth/revoke/; never shipped in a browser or mobile binary. - code
- The authorization code returned to your redirect URI after the user consents. Exchange it for tokens right away.
- access_token
- Bearer token for API calls, valid for 24 hours after issuance.
- refresh_token
- Used to obtain a new access token; valid for 365 days after initial issuance. A refresh response may contain a different refresh token.
- open_id
- The TikTok user's unique identifier, returned with the token. Use it as the key of the connected account.
- state
- A random value you create before the redirect and compare on callback to block CSRF.
How to set up TikTok OAuth for a web app
Register redirect URIs
Add up to 10 redirect URIs in the Login Kit settings. Each must be absolute, start with
https, be under 512 characters, be static (no query parameters) and contain no#fragment.Create and store a state value
Generate a random string for every login attempt and keep it in the user's session or a short-lived cookie. TikTok's own web sample uses a short-lived cookie too.
Redirect to the authorization page
Open
https://www.tiktok.com/v2/auth/authorize/withclient_key,response_type=code, a comma-separatedscope,redirect_uriandstate. Adddisable_auto_auth=1to always show the consent page.Validate the callback
TikTok returns
code,scopesandstate, orerroranderror_description. Reject the request ifstatedoes not match, and compare the grantedscopeswith what you asked for.Exchange the code on the server
POST to
https://open.tiktokapis.com/v2/oauth/token/asapplication/x-www-form-urlencodedwithclient_key,client_secret,code,grant_type=authorization_codeand the sameredirect_uri. Desktop and mobile apps also sendcode_verifier.Store tokens securely
Save
access_token,refresh_token, both expiry times andopen_id, encrypted at rest. TikTok's web guide makes the server explicitly responsible for keeping the client secret and refresh token secure.
Login Kit in Python
Framework-agnostic handlers: session is whatever session storage your web framework provides.
import secrets
from socialift import TikTok
REDIRECT_URI = "https://app.example.com/oauth/tiktok/callback"
SCOPES = ["user.info.basic", "video.list", "video.publish"]
async def begin_login(session: dict) -> str:
session["tiktok_state"] = secrets.token_urlsafe(32)
async with TikTok.from_env() as tt:
return tt.login_url(
redirect_uri=REDIRECT_URI,
state=session["tiktok_state"],
scopes=SCOPES,
)
async def finish_login(session: dict, code: str, state: str) -> dict:
expected = session.pop("tiktok_state", "")
if not expected or not secrets.compare_digest(state, expected):
raise ValueError("OAuth state mismatch")
async with TikTok.from_env() as tt:
bundle = await tt.exchange_code(code, redirect_uri=REDIRECT_URI)
return {
"open_id": bundle.raw.get("open_id"),
"access_token": bundle.access_token,
"refresh_token": bundle.refresh_token,
"expires_at": bundle.expires_at,
"scopes": bundle.scopes,
}exchange_code sends a form-urlencoded body, as TikTok requires, and raises ApiError if no access token comes back.
Store the result encrypted. The SDK keeps nothing between calls, so a single client instance can serve many creators.
PKCE: web vs desktop vs mobile
| Platform | PKCE | Redirect URI rules |
|---|---|---|
| Web | Not part of the Login Kit for Web guide; security relies on the server-side secret and state | Absolute https, static, no fragment, up to 10 URIs, under 512 characters |
| Desktop | Required: code_challenge and code_challenge_method=S256 in the URL, code_verifier at exchange | http or https, localhost or 127.0.0.1 allowed, port required (wildcards allowed), no parameters or fragment |
| iOS / Android | code_verifier required at token exchange | Based on the platform details registered for the app (package name, bundle ID) |
TikTok's desktop guide computes code_challenge as the hex-encoded SHA-256 of the verifier — not the base64url encoding used in RFC 7636.
PKCE for desktop apps
The verifier must be 43–128 characters long and use only A–Z, a–z, 0–9 and -, ., _, ~.
import hashlib
import secrets
from socialift import TikTok
REDIRECT_URI = "http://127.0.0.1:3455/callback/"
def make_pkce() -> tuple[str, str]:
verifier = secrets.token_urlsafe(48) # 64 characters
challenge = hashlib.sha256(verifier.encode()).hexdigest() # hex, as TikTok documents
return verifier, challenge
async def desktop_login(state: str) -> tuple[str, str]:
verifier, challenge = make_pkce()
async with TikTok.from_env() as tt:
url = tt.login_url(
redirect_uri=REDIRECT_URI,
state=state,
code_challenge=challenge,
code_challenge_method="S256",
)
return url, verifier
async def desktop_callback(code: str, verifier: str):
async with TikTok.from_env() as tt:
return await tt.exchange_code(code, redirect_uri=REDIRECT_URI, code_verifier=verifier)Run the code exchange on your backend: the token request still includes the client secret.
Token lifetimes and what to do about them
| Situation | Lifetime | What to do |
|---|---|---|
access_token | 24 hours after issuance | Refresh before it expires; the exact value is in expires_in |
refresh_token | 365 days after initial issuance | If the refresh response carries a different refresh token, replace the stored one |
| Refresh token expired | no | Send the user through Login Kit again |
| User removed your app | The access token is already invalid when authorization.removed arrives | Mark the account disconnected and stop calls |
/v2/oauth/revoke/ called | Your app's access is revoked | Delete the stored tokens |
yesno
Refresh daily, revoke on disconnect
from datetime import datetime, timedelta, timezone
from socialift import ApiError, TikTok
async def ensure_fresh(account: dict) -> dict:
left = account["expires_at"] - datetime.now(timezone.utc)
if left > timedelta(hours=2):
return account
async with TikTok.from_env() as tt:
try:
bundle = await tt.refresh_token(account["refresh_token"])
except ApiError:
account["status"] = "reconnect_required"
return account
account.update(
access_token=bundle.access_token,
refresh_token=bundle.refresh_token or account["refresh_token"],
expires_at=bundle.expires_at,
)
return account
async def disconnect(account: dict) -> None:
async with TikTok.from_env() as tt:
await tt.revoke(account["access_token"])Run the refresh from a cron job or queue at an interval shorter than 24 hours. Persist the refresh token from every response — TikTok requires you to use the new value when it changes.
Scopes and consent in practice
The scope parameter is a comma-separated list, and each scope must already be assigned to your app on the TikTok for Developers site. On callback TikTok reports the granted scopes in the scopes parameter — store it and check it before calling an endpoint that needs a specific scope.
When you add scopes later, send existing users through authorization again. With disable_auto_auth=0 TikTok skips the authorization page for valid sessions; with disable_auto_auth=1 it always shows it, which is what you want when the requested scopes change. In the SDK: tt.login_url(..., disable_auto_auth=True).
If a user removes your app in TikTok settings, a registered webhook receives authorization.removed; by then the access token is already invalid. The Socialift SDK verifies these deliveries with verify_tiktok_request and parses them with parse_tiktok_event. The exact redirect and webhook URLs to register with TikTok are shown on the Keys (Kalitlar) page of the Socialift dashboard.
TikTok OAuth security checklist
- Keep the client secret and refresh tokens on the server only, encrypted at rest.
- Generate a fresh
statefor every login and reject callbacks where it does not match. - Register exact, static HTTPS redirect URIs; carry context in the state or session, not in query parameters.
- Use PKCE with
S256in desktop apps and sendcode_verifierat exchange. - Refresh access tokens on a schedule shorter than 24 hours and persist rotated refresh tokens.
- Revoke tokens with
/v2/oauth/revoke/when a user disconnects inside your product. - Log the
log_idof failed API calls for TikTok support.
Frequently asked questions
How long is a TikTok access token valid?
The access token is valid for 24 hours after issuance and the refresh token for 365 days after initial issuance. An integration therefore needs a daily refresh job; once the refresh token expires, the user must authorize again.
Does TikTok return a new refresh token on refresh?
It can. TikTok's documentation says the returned refresh_token may differ from the one you sent, and in that case you must use the new value. Update the stored token after every refresh.
Is PKCE required for TikTok web login?
The Login Kit for Web guide does not use PKCE: the web flow relies on the server-side client secret and the state check. Desktop and mobile apps must send code_verifier during the code exchange; for desktop, code_challenge is a hex-encoded SHA-256.
Why do I get a redirect_uri error?
The redirect_uri in the request must exactly match a value registered in the app settings. For web it has to be an absolute https URL, static (no parameters) and without a # fragment; send the same value again during the code exchange.
How do I revoke a TikTok token?
Send a POST to https://open.tiktokapis.com/v2/oauth/revoke/ with client_key, client_secret and token. In the Socialift SDK that is await tt.revoke(access_token).
Which scope is granted by default?
user.info.basic is added automatically for apps using Login Kit; it returns open_id, avatar and display name. Every other scope has to be added to the app separately.