pythontiktoktiktok-api

TikTok API: "Code verifier or code challenge is invalid" despite correct PKCE implementation


I'm trying to integrate the TikTok API into my application using the PKCE (Proof Key for Code Exchange) flow for OAuth 2.0. Despite following the documentation and ensuring that the code_verifier and code_challenge are generated and passed correctly, I keep receiving the error:

{
  "error": "invalid_request",
  "error_description": "Code verifier or code challenge is invalid.",
  "log_id": "..."
}

Here's a summary of my implementation:

  1. Generating code_verifier and code_challenge:
    • The code_verifier is a high-entropy random string of 64 characters.
    • The code_challenge is the base64-url-encoded SHA-256 hash of the code_verifier.
import secrets
import hashlib
import base64
import requests
from flask import Flask, request, redirect, session
import re
import urllib.parse

app = Flask(__name__)
app.secret_key = secrets.token_urlsafe(64)

client_key = 'your_client_key'
client_secret = 'your_client_secret'
redirect_uri = 'http://127.0.0.1:5000/callback/'
scopes = 'video.upload'

def generate_random_string(length):
    characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~'
    return ''.join(secrets.choice(characters) for _ in range(length))

def generate_code_challenge_pair():
    code_verifier = generate_random_string(64)  # Generate a code_verifier with 64 characters
    sha256_hash = hashlib.sha256(code_verifier.encode('utf-8')).digest()
    code_challenge = base64.urlsafe_b64encode(sha256_hash).decode('utf-8').rstrip('=')
    return code_verifier, code_challenge

@app.route('/')
def home():
    code_verifier, code_challenge = generate_code_challenge_pair()
    state = generate_random_string(16)
    
    session['code_verifier'] = code_verifier
    session['state'] = state
    
    tiktok_auth_url = (
        f'https://www.tiktok.com/v2/auth/authorize/?'
        f'client_key={client_key}&scope={scopes}&response_type=code&'
        f'redirect_uri={redirect_uri}&state={state}&'
        f'code_challenge={code_challenge}&code_challenge_method=S256'
    )
    return redirect(tiktok_auth_url)

@app.route('/callback/')
def callback():
    code = request.args.get('code')
    state = request.args.get('state')
    
    if state != session.get('state'):
        return 'CSRF verification failed', 400
    
    code_verifier = session.get('code_verifier')
    if not code_verifier:
        return 'Missing code verifier', 400

    decoded_code = urllib.parse.unquote(code)

    access_token = get_access_token(decoded_code, code_verifier)
    if access_token:
        return f'Authentication successful! Access token: {access_token}'
    else:
        return 'Failed to obtain access token', 400

def get_access_token(code, code_verifier):
    url = 'https://open.tiktokapis.com/v2/oauth/token/'
    headers = {
        'Content-Type': 'application/x-www-form-urlencoded'
    }
    payload = {
        'client_key': client_key,
        'client_secret': client_secret,
        'code': code,
        'grant_type': 'authorization_code',
        'redirect_uri': redirect_uri,
        'code_verifier': code_verifier
    }
    response = requests.post(url, headers=headers, data=payload)
    data = response.json()
    if 'access_token' in data:
        return data['access_token']
    return None

if __name__ == '__main__':
    app.run(port=5000, debug=True)
  1. Redirecting to TikTok for authorization:

    • The user is redirected to TikTok's authorization URL with the necessary parameters, including code_challenge and state.
  2. Handling the callback:

    • The authorization code is captured and the state is verified.
    • The code_verifier is then used to request an access token.

Despite this setup, the response from TikTok's token endpoint consistently states that the "Code verifier or code challenge is invalid."

I've double-checked the generation and passing of both the code_verifier and code_challenge. Here are some example values for reference:

Does anyone have experience with TikTok's PKCE implementation or see where I might be going wrong? Any help would be greatly appreciated!



Solution

  • As suggested by user Patola, the issue was that the SHA-256 hash needed to be converted to a hex string after the hash was created. The correct function for generating the code_verifier and code_challenge pair should look like this:

    def generate_code_challenge_pair():
        code_verifier = generate_random_string(60)
        sha256_hash = hashlib.sha256(code_verifier.encode('utf-8')).digest()
        code_challenge = sha256_hash.hex()  # Convert to hex string
        return code_verifier, code_challenge
    

    After making this adjustment, the API no longer returns the "Code verifier or code challenge is invalid" error.