TapTap OAuth API
Overview
TapTap OpenAPI uses MAC Token signatures to authenticate API requests.
Basic Concepts
- Access Token: A credential object returned by the SDK after the user completes TapTap login authorization, containing fields such as
kidandmac_key. - MAC Token: An Authorization request header generated from Access Token fields using the HMAC-SHA1 algorithm, used for identity verification when calling TapTap OpenAPI.
- openid: TapTap user + Client ID =
openid(the same user has different openid values across different games) - unionid: TapTap user + developer account =
unionid(the same user has the same unionid across all games under the same developer)
Field Name Mapping
The SDK and Authorization header use different names for the same fields. The mapping is as follows:
| SDK Access Token Field | Name in Authorization Header | Description |
|---|---|---|
kid | id in MAC id="..." | Identifies the Access Token for this authorization, returned by the SDK after each login |
mac_key | (Does not appear in the request header) | The key used for HMAC-SHA1 signing, returned by the SDK after each login. Note that this is a different value from Server Secret in the developer console |
Access Token
After developers integrate the SDK login module and the user authorizes the application, an Access Token can be obtained. The game mobile client can retrieve the current valid Access Token through the GetCurrentTapAccount method.
Access Token contains the following fields:
public String kid; // Access Token identifier, used as the MAC id field in the Authorization header
public String token_type; // Token type, e.g., "mac"
public String mac_key; // Key used for HMAC-SHA1 signing
public String mac_algorithm; // Signing algorithm, e.g., "hmac-sha-1"
public Set<String> scopes; // Authorization scope, e.g., "basic_info" or "public_profile"
- The maximum validity period of a single Access Token is 30 days. However, an Access Token may become invalid earlier in certain circumstances, such as when the user deletes their account or revokes authorization in the TapTap client. Integrators should handle Token invalidation scenarios to avoid making requests with revoked Tokens.
- The SDK automatically refreshes the Access Token each time the application launches, ensuring continued validity of the user session.
- Note: It is not recommended to cache Access Tokens on the server or game client. When needed, obtain the latest Access Token through the SDK.
See the MAC Token Signing section for the MAC Token signing method.
API
When the SDK only requests basic_info permission, use the basic info interface. When requesting public_profile, use the detailed info interface.
Get Current Account Basic Info
GET https://open.tapapis.com/account/basic-info/v1?client_id=xxx
Authorization mac token
Request Parameters
| Field | Type | Description |
|---|---|---|
| client_id | string | The application's Client ID, must match the configured value |
Response Parameters
| Field | Type | Description |
|---|---|---|
| openid | string | Unique identifier of the authorized user. Each player has a different openid in each game, but the same player always gets the same openid in the same game |
| unionid | string | Unique identifier of the authorized user. A player has the same unionid across all games under the same developer, but different unionid across different developers |
Request Example
Replace kid (the MAC id="..." value in the Authorization header), mac_key (used for signing), and Client ID with actual values.
curl -s -H 'Authorization:MAC id="1/hC0vtMo7ke0Hkd-iI8-zcAwy7vKds9si93l7qBmNFxJkylWEOYEzGqa7k_9iw_bb3vizf-3CHc6U8hs-5a74bMFzkkz7qC2HdifBEHsW9wxOBn4OsF9vz4Cc6CWijkomnOHdwt8Km6TywOX5cxyQv0fnQQ9fEHbptkIJa
gCd33eBXg76grKmKsIR-YUZd1oVHu0aZ6BR7tpYYsCLl-LM6ilf8LZpahxQ28n2c-y33d-20YRY5NW1SnR7BorFbd00ZP97N9kwDncoM1GvSZ7n90_0ZWj4a12x1rfAWLuKEimw1oMGl574L0wE5mGoshPa-CYASaQmBDo3Q69XbjTs
KQ",ts="1618221750",nonce="adssd",mac="XWTPmq6A6LzgK8BbNDwj+kE4gzs="' "https://open.tapapis.com/account/basic-info/v1?client_id=<Client ID>"
Get Current Account Detailed Info
GET https://open.tapapis.com/account/profile/v1?client_id=xxx
Authorization mac token
Request Parameters
| Field | Type | Description |
|---|---|---|
| client_id | string | The application's Client ID, must match the configured value |
Response Parameters
| Field | Type | Description |
|---|---|---|
| name | string | Username |
| avatar | string | Avatar URL |
| openid | string | Unique identifier of the authorized user. Each player has a different openid in each game, but the same player always gets the same openid in the same game |
| unionid | string | Unique identifier of the authorized user. A player has the same unionid across all games under the same developer, but different unionid across different developers |
Request Example
Replace kid (the MAC id="..." value in the Authorization header), mac_key (used for signing), and Client ID with actual values.
curl -s -H 'Authorization:MAC id="1/hC0vtMo7ke0Hkd-iI8-zcAwy7vKds9si93l7qBmNFxJkylWEOYEzGqa7k_9iw_bb3vizf-3CHc6U8hs-5a74bMFzkkz7qC2HdifBEHsW9wxOBn4OsF9vz4Cc6CWijkomnOHdwt8Km6TywOX5cxyQv0fnQQ9fEHbptkIJa
gCd33eBXg76grKmKsIR-YUZd1oVHu0aZ6BR7tpYYsCLl-LM6ilf8LZpahxQ28n2c-y33d-20YRY5NW1SnR7BorFbd00ZP97N9kwDncoM1GvSZ7n90_0ZWj4a12x1rfAWLuKEimw1oMGl574L0wE5mGoshPa-CYASaQmBDo3Q69XbjTs
KQ",ts="1618221750",nonce="adssd",mac="XWTPmq6A6LzgK8BbNDwj+kE4gzs="' "https://open.tapapis.com/account/profile/v1?client_id=<Client ID>"
MAC Token Signing
The kid and mac_key required for signing both come from the Access Token returned by the client SDK after login. See Field Name Mapping for field definitions.
Signing Steps
-
Construct the signing string in the following format (each field separated by
\n):{timestamp}\n{nonce}\n{method}\n{uri}\n{host}\n{port}\n\ntimestamp: Current timestamp (in seconds)nonce: Random stringmethod: HTTP request method (e.g., GET, POST)uri: Request path (including query string, e.g.,/account/profile/v1?client_id=xxx)host: Request domain (e.g.,open.tapapis.com)port: Port number (443 for HTTPS)
-
Use
mac_keyto perform HMAC-SHA1 signing on the signing string, then Base64-encode the result to get themacvalue. -
Construct the Authorization header:
MAC id="{kid}",ts="{timestamp}",nonce="{nonce}",mac="{mac}"
Code Examples
Node.js Request Example
const http = require('http');
const https = require('https');
const crypto = require('crypto');
/**
* Configuration: replace the following parameters with actual values before making authorization requests.
*/
const client_id = "Replace with Client ID from the console";
const kid = "Replace with kid from the Access Token returned by the client SDK";
const macKey = "Replace with mac_key from the Access Token returned by the client SDK";
/**
* Endpoint for getting current account detailed info.
* To get basic account info, replace with:
* https://open.tapapis.com/account/basic-info/v1
*/
const url = "https://open.tapapis.com/account/profile/v1";
/**
* Main program logic
*/
// Step 1: Set request method and URL
const method = 'GET';
const requestUrl = url + '?client_id=' + client_id;
// Step 2: Generate timestamp and nonce
const ts = Math.floor(Date.now() / 1000).toString().padStart(10, '0');
const nonce = getRandomString(5);
// Step 3: Build signing string and generate signature
const parsedUrl = new URL(requestUrl);
const host = parsedUrl.hostname;
const uri = parsedUrl.pathname + parsedUrl.search;
const port = parsedUrl.port || (parsedUrl.protocol === 'https:' ? '443' : '80');
const signingString = buildSigningString(ts, nonce, method, uri, host, port);
const mac = sign(signingString, macKey);
// Step 4: Generate Authorization header
const authorization = `MAC id="${kid}",ts="${ts}",nonce="${nonce}",mac="${mac}"`;
console.log('Authorization: ' + authorization);
// Step 5: Execute HTTP request and output result
const client = parsedUrl.protocol === 'https:' ? https : http;
const req = client.request({
hostname: host,
port: port,
path: uri,
method: method,
headers: { 'Authorization': authorization }
}, (res) => {
let data = '';
res.on('data', (chunk) => { data += chunk; });
res.on('end', () => { console.log(data); });
});
req.end();
/**
* Generate a random string
* @param {number} length - Length of the random string
* @returns {string} Randomly generated string (alphanumeric only)
*/
function getRandomString(length) {
const chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
const bytes = crypto.randomBytes(length);
let result = '';
for (let i = 0; i < length; i++) {
result += chars[bytes[i] % chars.length];
}
return result;
}
/**
* Build the signing string
* @param {string} ts - Timestamp
* @param {string} nonce - Random string
* @param {string} method - HTTP method
* @param {string} uri - Request path (including query string)
* @param {string} host - Request domain
* @param {string} port - Port number
* @returns {string} The signing string
*/
function buildSigningString(ts, nonce, method, uri, host, port) {
return `${ts}\n${nonce}\n${method}\n${uri}\n${host}\n${port}\n\n`;
}
/**
* Generate signature using HMAC-SHA1
* @param {string} signingString - The signing string
* @param {string} key - MAC key
* @returns {string} Base64-encoded signature value
*/
function sign(signingString, key) {
const hmac = crypto.createHmac('sha1', key);
hmac.update(signingString);
return hmac.digest('base64');
}
Java Request Example
package com.taptap;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.*;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
public class Authorization {
public static void main(String[] args) throws IOException {
/**
* Configuration: replace the following parameters with actual values before making authorization requests.
*/
String client_id = "Replace with Client ID from the console";
String kid = "Replace with kid from the Access Token returned by the client SDK";
String mac_key = "Replace with mac_key from the Access Token returned by the client SDK";
/**
* Endpoint for getting current account detailed info.
* To get basic account info, replace with:
* https://open.tapapis.com/account/basic-info/v1
*/
String url = "https://open.tapapis.com/account/profile/v1";
/**
* Main program logic
*/
// Step 1: Set request method and URL
String method = "GET";
String request_url = url + "?client_id=" + client_id;
// Step 2: Generate timestamp and nonce
String ts = String.format(Locale.US, "%010d", System.currentTimeMillis() / 1000);
String nonce = getRandomString(5);
// Step 3: Build signing string and generate signature
URL parsedUrl = new URL(request_url);
String host = parsedUrl.getHost();
String uri = request_url.substring(request_url.lastIndexOf(host) + host.length());
String port = request_url.startsWith("https") ? "443" : "80";
String signingString = buildSigningString(ts, nonce, method, uri, host, port);
String mac = sign(signingString, mac_key);
// Step 4: Generate Authorization header
String authorization = String.format("MAC id=\"%s\",ts=\"%s\",nonce=\"%s\",mac=\"%s\"",
kid, ts, nonce, mac);
System.out.println("Authorization: " + authorization);
// Step 5: Execute HTTP request and output result
HttpURLConnection conn = (HttpURLConnection) new URL(request_url).openConnection();
conn.setRequestProperty("Authorization", authorization);
conn.setRequestMethod(method);
BufferedReader rd = new BufferedReader(new InputStreamReader(conn.getInputStream()));
String line;
StringBuilder result = new StringBuilder();
while ((line = rd.readLine()) != null) {
result.append(line);
}
rd.close();
System.out.println(result.toString());
}
/**
* Generate a random string
* @param length Length of the random string
* @return Randomly generated string (alphanumeric only)
*/
private static String getRandomString(int length) {
String chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
SecureRandom random = new SecureRandom();
StringBuilder sb = new StringBuilder(length);
for (int i = 0; i < length; i++) {
sb.append(chars.charAt(random.nextInt(chars.length())));
}
return sb.toString();
}
/**
* Build the signing string
* @param ts Timestamp
* @param nonce Random string
* @param method HTTP method
* @param uri Request path (including query string)
* @param host Request domain
* @param port Port number
* @return The signing string
*/
private static String buildSigningString(String ts, String nonce, String method,
String uri, String host, String port) {
return ts + "\n" + nonce + "\n" + method + "\n" + uri + "\n" + host + "\n" + port + "\n\n";
}
/**
* Generate signature using HMAC-SHA1
* @param signingString The signing string
* @param key MAC key
* @return Base64-encoded signature value
*/
private static String sign(String signingString, String key) {
try {
SecretKeySpec signingKey = new SecretKeySpec(key.getBytes(), "HmacSHA1");
Mac mac = Mac.getInstance("HmacSHA1");
mac.init(signingKey);
byte[] rawHmac = mac.doFinal(signingString.getBytes(StandardCharsets.UTF_8));
return Base64.getEncoder().encodeToString(rawHmac);
} catch (NoSuchAlgorithmException | InvalidKeyException e) {
throw new IllegalStateException(e);
}
}
}
PHP Request Example
<?php
/**
* Configuration: replace the following parameters with actual values before making authorization requests.
*/
$client_id = "Replace with Client ID from the console";
$kid = "Replace with kid from the Access Token returned by the client SDK";
$mac_key = "Replace with mac_key from the Access Token returned by the client SDK";
/**
* Endpoint for getting current account detailed info.
* To get basic account info, replace with:
* https://open.tapapis.com/account/basic-info/v1
*/
$url = "https://open.tapapis.com/account/profile/v1";
/**
* Main program logic
*/
// Step 1: Set $method and $request_url
$method = "GET";
$request_url = $url . "?client_id=" . urlencode($client_id);
// Step 2: Generate timestamp and nonce
$ts = time(); // Current timestamp in seconds
$nonce = randomString(5); // Random string, at least 5 characters
// Step 3: Build signing string and generate signature
$signing_string = createSigningString($request_url, $ts, $nonce, $method);
$mac = sign($signing_string, $mac_key);
// Step 4: Generate Authorization header
$auth = sprintf('MAC id="%s",ts="%s",nonce="%s",mac="%s"', $kid, $ts, $nonce, $mac);
echo "Authorization: " . $auth . PHP_EOL . PHP_EOL;
// Step 5: Execute HTTP request and output result
$headers = array("Authorization: " . $auth);
$response = executeCurlRequest($request_url, $headers, $method);
echo "HTTP Status Code: " . $response['http_code'] . PHP_EOL . PHP_EOL;
if (isset($response['error'])) {
echo "Error: " . $response['error'] . PHP_EOL . PHP_EOL;
}
if (isset($response['body'])) {
echo "Response Body: " . PHP_EOL . json_encode($response['body'], JSON_PRETTY_PRINT) . PHP_EOL;
}
/**
* Build the signing string
*
* @param string $request_url Request URL
* @param int $ts Timestamp
* @param string $nonce Random string
* @param string $method HTTP method
* @return string The signing string
*/
function createSigningString($request_url, $ts, $nonce, $method)
{
$parsed_url = parse_url($request_url);
$uri = $parsed_url['path'] . '?' . $parsed_url['query'];
$domain = $parsed_url['host'];
$port = 443; // Fixed port for HTTPS
return implode("\n", [
$ts,
$nonce,
$method,
$uri,
$domain,
$port,
""
]) . "\n";
}
/**
* Generate signature value
*
* @param string $signing_string The signing string
* @param string $mac_key MAC key
* @return string Generated signature value
* @example sign('abc', 'def') -> dYTuFEkwcs2NmuhQ4P8JBTgjD4w=
*/
function sign($signing_string, $mac_key)
{
return base64_encode(hash_hmac('sha1', $signing_string, $mac_key, true));
}
/**
* Execute cURL request and return result
*
* @param string $url Request URL
* @param array $headers Request headers
* @param string $method HTTP method
* @return array Array containing status code and response content or error information
*/
function executeCurlRequest($url, $headers, $method)
{
$curl = curl_init($url);
curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $method);
curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
// Execute request and capture response
$response = curl_exec($curl);
$httpCode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
// Check for cURL errors
if (curl_errno($curl)) {
$error_message = curl_error($curl);
curl_close($curl);
return array(
'http_code' => 0,
'error' => "cURL error: " . $error_message,
'body' => json_decode($response, true)
);
}
// Close cURL handle
curl_close($curl);
// Parse response
$parsed_body = json_decode($response, true);
// Return result based on HTTP status code
if ($httpCode >= 200 && $httpCode < 300) {
return array(
'http_code' => $httpCode,
'body' => $parsed_body
);
} else {
return array(
'http_code' => $httpCode,
'error' => "HTTP request failed, status code: $httpCode",
'body' => $parsed_body
);
}
}
/**
* Generate a random string
*
* @param int $length Length of the random string
* @return string Randomly generated string
*/
function randomString($length = 5)
{
$characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
$charactersLength = strlen($characters);
$randomString = '';
for ($i = 0; $i < $length; $i++) {
$randomIndex = mt_rand(0, $charactersLength - 1);
$randomString .= $characters[$randomIndex];
}
return $randomString;
}
Python3 Request Example
import base64
import hmac
import random
import string
import time
from hashlib import sha1
from urllib.parse import urlparse
from urllib.request import Request, urlopen
#
# Configuration: replace the following parameters with actual values before making authorization requests.
#
client_id = "Replace with Client ID from the console"
kid = "Replace with kid from the Access Token returned by the client SDK"
mac_key = "Replace with mac_key from the Access Token returned by the client SDK"
#
# Endpoint for getting current account detailed info.
# To get basic account info, replace with:
# https://open.tapapis.com/account/basic-info/v1
#
url = "https://open.tapapis.com/account/profile/v1"
#
# Main program logic
#
def main():
# Step 1: Set request method and URL
method = 'GET'
request_url = f'{url}?client_id={client_id}'
# Step 2: Generate timestamp and nonce
ts = str(int(time.time()))
nonce = ''.join(random.choices(string.ascii_letters + string.digits, k=5))
# Step 3: Build signing string and generate signature
parsed = urlparse(request_url)
signing_string = build_signing_string(
ts, nonce, method, parsed.path + '?' + parsed.query, parsed.hostname, '443')
mac = sign(signing_string, mac_key)
# Step 4: Generate Authorization header
authorization = f'MAC id="{kid}",ts="{ts}",nonce="{nonce}",mac="{mac}"'
print('Authorization:', authorization)
# Step 5: Execute HTTP request and output result
req = Request(request_url, headers={'Authorization': authorization})
with urlopen(req) as response:
print(response.read().decode('UTF-8'))
def build_signing_string(ts, nonce, method, uri, host, port):
"""Build the signing string
Args:
ts: Timestamp
nonce: Random string
method: HTTP method
uri: Request path (including query string)
host: Request domain
port: Port number
Returns:
The signing string
"""
return '\n'.join([ts, nonce, method, uri, host, port, '']) + '\n'
def sign(signing_string, key):
"""Generate signature using HMAC-SHA1
Args:
signing_string: The signing string
key: MAC key
Returns:
Base64-encoded signature value
"""
hmac_code = hmac.new(key.encode('UTF-8'), signing_string.encode('UTF-8'), sha1)
return base64.b64encode(hmac_code.digest()).decode('UTF-8')
if __name__ == '__main__':
main()
Go Request Example
package main
import (
"crypto/hmac"
"crypto/rand"
"crypto/sha1"
"encoding/base64"
"fmt"
"io"
"net/http"
"strconv"
"time"
)
func main() {
/*
* Configuration: replace the following parameters with actual values before making authorization requests.
*/
clientId := "Replace with Client ID from the console"
kid := "Replace with kid from the Access Token returned by the client SDK"
macKey := "Replace with mac_key from the Access Token returned by the client SDK"
reqHost := "open.tapapis.com"
/*
* Endpoint for getting current account detailed info.
* To get basic account info, replace the path below with:
* /account/basic-info/v1
*/
reqURI := "/account/profile/v1?client_id=" + clientId
reqURL := "https://" + reqHost + reqURI
/*
* Main program logic
*/
// Step 1: Set request method and URL
method := "GET"
// Step 2: Generate timestamp and nonce
timestamp := strconv.FormatInt(time.Now().Unix(), 10)
nonce := randomString(5)
// Step 3: Build signing string and generate signature
signingString := buildSigningString(timestamp, nonce, method, reqURI, reqHost, "443")
mac := hmacSha1(signingString, macKey)
// Step 4: Generate Authorization header
authorization := fmt.Sprintf(`MAC id="%s",ts="%s",nonce="%s",mac="%s"`, kid, timestamp, nonce, mac)
fmt.Println("Authorization:", authorization)
// Step 5: Execute HTTP request and output result
client := http.Client{}
req, err := http.NewRequest(method, reqURL, nil)
if err != nil {
fmt.Println(err.Error())
return
}
req.Header.Add("Authorization", authorization)
resp, err := client.Do(req)
if err != nil {
fmt.Println(err.Error())
return
}
defer resp.Body.Close()
respBody, err := io.ReadAll(resp.Body)
if err != nil {
fmt.Println(err.Error())
return
}
fmt.Println(string(respBody))
}
// buildSigningString builds the signing string
//
// Parameters:
// - ts: Timestamp
// - nonce: Random string
// - method: HTTP method
// - uri: Request path (including query string)
// - host: Request domain
// - port: Port number
func buildSigningString(ts, nonce, method, uri, host, port string) string {
return ts + "\n" + nonce + "\n" + method + "\n" + uri + "\n" + host + "\n" + port + "\n\n"
}
// hmacSha1 generates a signature using HMAC-SHA1, returns Base64-encoded signature value
//
// Parameters:
// - signingString: The signing string
// - key: MAC key
func hmacSha1(signingString, key string) string {
mac := hmac.New(sha1.New, []byte(key))
mac.Write([]byte(signingString))
return base64.StdEncoding.EncodeToString(mac.Sum(nil))
}
// randomString generates a random string
//
// Parameters:
// - length: Length of the random string
func randomString(length int) string {
const chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
b := make([]byte, length)
rand.Read(b)
for i := range b {
b[i] = chars[b[i]%byte(len(chars))]
}
return string(b)
}
C# Request Example
using System.Collections;
using UnityEngine;
using System;
using System.Net.Http;
using System.Text;
public class TapLoginOAuth : MonoBehaviour
{
/**
* Configuration: replace the following parameters with actual values before making authorization requests.
*/
private string clientId = "Replace with Client ID from the console";
private string kid = "Replace with kid from the Access Token returned by the client SDK";
private string macKey = "Replace with mac_key from the Access Token returned by the client SDK";
/**
* Endpoint for getting current account detailed info.
* To get basic account info, replace with:
* https://open.tapapis.com/account/basic-info/v1
*/
private string url = "https://open.tapapis.com/account/profile/v1";
void Start()
{
StartCoroutine(SendRequest());
}
IEnumerator SendRequest()
{
/**
* Main program logic
*/
// Step 1: Set request method and URL
string method = "GET";
string requestUrl = url + "?client_id=" + clientId;
// Step 2: Generate timestamp and nonce
string ts = ((int)(DateTime.UtcNow - new DateTime(1970, 1, 1)).TotalSeconds).ToString();
string nonce = GenerateNonce(5);
// Step 3: Build signing string and generate signature
var parsedUrl = new Uri(requestUrl);
string host = parsedUrl.Host;
string uri = parsedUrl.PathAndQuery;
string signingString = BuildSigningString(ts, nonce, method, uri, host, "443");
string mac = Sign(signingString, macKey);
// Step 4: Generate Authorization header
string authorization = string.Format("MAC id=\"{0}\",ts=\"{1}\",nonce=\"{2}\",mac=\"{3}\"",
kid, ts, nonce, mac);
Debug.Log("Authorization: " + authorization);
// Step 5: Execute HTTP request and output result
using (var httpClient = new HttpClient())
{
var request = new HttpRequestMessage(HttpMethod.Get, new Uri(requestUrl));
request.Headers.Add("Authorization", authorization);
var response = httpClient.SendAsync(request).Result;
var responseBody = response.Content.ReadAsStringAsync().Result;
Debug.Log("Response: " + responseBody);
}
yield return null;
}
/// <summary>
/// Build the signing string
/// </summary>
/// <param name="ts">Timestamp</param>
/// <param name="nonce">Random string</param>
/// <param name="method">HTTP method</param>
/// <param name="uri">Request path (including query string)</param>
/// <param name="host">Request domain</param>
/// <param name="port">Port number</param>
/// <returns>The signing string</returns>
string BuildSigningString(string ts, string nonce, string method, string uri, string host, string port)
{
string[] signArray = { ts, nonce, method, uri, host, port, "" };
return string.Join("\n", signArray) + "\n";
}
/// <summary>
/// Generate signature using HMAC-SHA1
/// </summary>
/// <param name="signingString">The signing string</param>
/// <param name="key">MAC key</param>
/// <returns>Base64-encoded signature value</returns>
string Sign(string signingString, string key)
{
using (var hmac = new System.Security.Cryptography.HMACSHA1(Encoding.UTF8.GetBytes(key)))
{
byte[] hash = hmac.ComputeHash(Encoding.UTF8.GetBytes(signingString));
return Convert.ToBase64String(hash);
}
}
/// <summary>
/// Generate a random string
/// </summary>
/// <param name="length">Length of the random string</param>
/// <returns>Randomly generated string</returns>
string GenerateNonce(int length)
{
const string chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
var random = new System.Random();
var nonce = new char[length];
for (int i = 0; i < length; i++)
{
nonce[i] = chars[random.Next(chars.Length)];
}
return new string(nonce);
}
}
C++ Request Example
#include <iostream>
#include <string>
#include <sstream>
#include <ctime>
#include <random>
#include <curl/curl.h>
#include <openssl/hmac.h>
#include <openssl/evp.h>
/**
* Configuration: replace the following parameters with actual values before making authorization requests.
*/
const std::string client_id = "Replace with Client ID from the console";
const std::string kid = "Replace with kid from the Access Token returned by the client SDK";
const std::string mac_key = "Replace with mac_key from the Access Token returned by the client SDK";
/**
* Endpoint for getting current account detailed info.
* To get basic account info, replace with:
* https://open.tapapis.com/account/basic-info/v1
*/
const std::string url = "https://open.tapapis.com/account/profile/v1";
// Function declarations
std::string createSigningString(const std::string& request_url, time_t ts,
const std::string& nonce, const std::string& method);
std::string sign(const std::string& signing_string, const std::string& key);
std::string randomString(int length);
std::string base64Encode(const unsigned char* input, unsigned int length);
size_t writeCallback(void* contents, size_t size, size_t nmemb, void* userp);
int main()
{
/**
* Main program logic
*/
// Step 1: Set method and request_url
std::string method = "GET";
std::string request_url = url + "?client_id=" + client_id;
// Step 2: Generate timestamp and nonce
time_t ts = time(nullptr); // Current timestamp in seconds
std::string nonce = randomString(5); // Random string, at least 5 characters
// Step 3: Build signing string and generate signature
std::string signing_string = createSigningString(request_url, ts, nonce, method);
std::string mac = sign(signing_string, mac_key);
// Step 4: Generate Authorization header
std::ostringstream auth_stream;
auth_stream << "MAC id=\"" << kid << "\",ts=\"" << ts
<< "\",nonce=\"" << nonce << "\",mac=\"" << mac << "\"";
std::string auth = auth_stream.str();
std::cout << "Authorization: " << auth << std::endl << std::endl;
// Step 5: Execute HTTP request and output result
CURL* curl = curl_easy_init();
if (!curl) {
std::cerr << "Error: curl init failed" << std::endl;
return 1;
}
std::string response_body;
struct curl_slist* headers = curl_slist_append(nullptr, ("Authorization: " + auth).c_str());
curl_easy_setopt(curl, CURLOPT_URL, request_url.c_str());
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeCallback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response_body);
CURLcode ret = curl_easy_perform(curl);
long http_code = 0;
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);
std::cout << "HTTP Status Code: " << http_code << std::endl << std::endl;
if (ret != CURLE_OK) {
std::cerr << "Error: cURL error: " << curl_easy_strerror(ret) << std::endl;
}
std::cout << "Response Body: " << std::endl << response_body << std::endl;
curl_slist_free_all(headers);
curl_easy_cleanup(curl);
return 0;
}
/**
* Build the signing string
*
* @param request_url Request URL
* @param ts Timestamp
* @param nonce Random string
* @param method HTTP method
* @return The signing string
*/
std::string createSigningString(const std::string& request_url, time_t ts,
const std::string& nonce, const std::string& method)
{
// Parse URL: extract domain, path, and query parameters
size_t scheme_end = request_url.find("://");
size_t host_start = (scheme_end != std::string::npos) ? scheme_end + 3 : 0;
size_t path_start = request_url.find('/', host_start);
std::string domain = request_url.substr(host_start, path_start - host_start);
std::string uri = request_url.substr(path_start);
int port = 443; // Fixed port for HTTPS
std::ostringstream oss;
oss << ts << "\n" << nonce << "\n" << method << "\n"
<< uri << "\n" << domain << "\n" << port << "\n\n";
return oss.str();
}
/**
* Generate signature value
*
* @param signing_string The signing string
* @param key MAC key
* @return Generated signature value
* @example sign("abc", "def") -> dYTuFEkwcs2NmuhQ4P8JBTgjD4w=
*/
std::string sign(const std::string& signing_string, const std::string& key)
{
unsigned char digest[EVP_MAX_MD_SIZE];
unsigned int digest_len = 0;
HMAC(EVP_sha1(),
key.c_str(), key.length(),
reinterpret_cast<const unsigned char*>(signing_string.c_str()), signing_string.length(),
digest, &digest_len);
return base64Encode(digest, digest_len);
}
/**
* Generate a random string
*
* @param length Length of the random string
* @return Randomly generated string
*/
std::string randomString(int length)
{
const std::string characters = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
std::default_random_engine generator(static_cast<unsigned long>(time(nullptr)));
std::uniform_int_distribution<int> distribution(0, characters.size() - 1);
std::string result;
for (int i = 0; i < length; ++i) {
result += characters[distribution(generator)];
}
return result;
}
/**
* Base64 encoding
*
* @param input Input data
* @param length Input data length
* @return Base64-encoded string
*/
std::string base64Encode(const unsigned char* input, unsigned int length)
{
int encoded_size = 4 * ((length + 2) / 3);
std::string output(encoded_size + 1, '\0');
EVP_EncodeBlock(reinterpret_cast<unsigned char*>(&output[0]), input, length);
output.resize(encoded_size);
return output;
}
/**
* cURL write callback function
*/
size_t writeCallback(void* contents, size_t size, size_t nmemb, void* userp)
{
static_cast<std::string*>(userp)->append(static_cast<char*>(contents), size * nmemb);
return size * nmemb;
}
Error Codes
Unified Format
| Field | Type | Description |
|---|---|---|
| code | int | Reserved field for future issue tracking |
| error | string | Error code, used for programmatic logic |
| error_description | string | Error description to help understand and resolve errors during development |
Error Responses
| Error Code | Description |
|---|---|
| invalid_request | The request is missing a required parameter, includes an unsupported parameter or parameter value, or is malformed |
| invalid_time | The ts time in the MAC Token algorithm is invalid. Re-construct with server time |
| invalid_client | The client_id parameter is invalid |
| access_denied | The authorization server denied the request. This status occurs when requesting user resources with a token. If encountered, the client should clear local user login information and redirect the user to re-login |
| forbidden | The user does not have permission for the current action. Re-authentication will not help, and this request should not be resubmitted |
| not_found | The request failed because the requested resource was not found on the server. Should not be retried with the same parameters |
| server_error | A server error occurred. You may retry after a short delay, but with a maximum retry limit of 3 attempts. If it continues to fail, abort and notify the user |
| insufficient_scope | The authorization scope used on the mobile client does not match the OAuth API called on the server side. For example, the mobile client authorized with basic_info permission but the server called the Get Current Account Detailed Info API |
FAQ
Calling TapTap OAuth interface returns access_denied error code.
- Check if the signature calculation is correct
Please use the sample scripts provided in the official TapTap documentation to verify that the signature calculation is correct. Incorrect signing may cause authentication failure.
- Access Token expired or invalid
Each Access Token has a maximum validity of 30 days. The SDK automatically refreshes the Token on each launch, ensuring that the Token obtained via GetCurrentTapAccount is always valid. If a player has not logged in or been active for 30 days, a new Token may not be obtainable locally, requiring the user to re-login.
- Player actively deregistered or revoked authorization
If a player deregisters their account in the TapTap client, or revokes authorization in the authorization management, subsequent requests will return an access_denied error. The server should properly handle access_denied when Token validation fails and redirect the user to re-login.
- Best practice: avoid caching Tokens
Do not cache Access Tokens locally, as Tokens may become invalid at any time. Each time you need to use one, obtain the latest valid Token through GetCurrentTapAccount to avoid unnecessary frequent login prompts for users.
Calling TapTap OAuth interface returns insufficient_scope error code.
The insufficient_scope error is typically caused by a mismatch between the mobile client's authorization scope and the permissions required by the server-side OAuth API.
For example, if the mobile client requests basic_info permission during TapTap authorization login, but the server calls the Get Current Account Detailed Info API which requires a higher permission level, the server will return this error code due to insufficient permissions.
Permission matching rules:
- If the mobile client only requests
basic_infopermission during authorization, the server can only call the Get Current Account Basic Info API. - If the mobile client requests
public_profilepermission during authorization, the server can call both the Get Current Account Basic Info API and the Get Current Account Detailed Info API.
By properly configuring OAuth authorization scopes, you can effectively avoid insufficient_scope errors and ensure smooth API calls.