Token Validation Mechanism
WebApps use a token validation mechanism to ensure the integrity and authenticity of the data passed to the WebApp. This is done through a hash-based signature validation process.
YoPhone provides an initData
parameter, which contains signed user information. The WebApp must validate this data to prevent tampering or unauthorized access.
#
Steps for Token Validation- Extract
initData
When a WebApp is launched, it provides an initData string, which includes:- User ID, username, and other details
- Timestamp of the session
- Hash signature
Example initData
:
auth_date=1234567890&hash=53a0fd101d226d24139793ebdca6cf0bfba3c062ab52c97eabf6ce163c65ca29&query_id=72d4e9cc-f80a-4822-b109-6db1046685eb&user={"first_name":"yo","id":"0192bcf9-4dda-7843-99a1-14535971bc14","language_code":"en","last_name":""}
- Compute the Valid Hash
To verify the integrity of the data:- Sort all parameters (except
hash
) in alphabetical order. - Concatenate them into a string in
key=value
format. - Generate an HMAC-SHA256 hash using the bot’s access token as the secret key.
- Sort all parameters (except
#
Token Validation ExamplesUse one of the following language examples to validate initData
.
- PHP
- Node.js
- Python
- Go
<?php // The initialization data received (replace {initData} with actual data) $initData = '{initData}'; $botAccessToken = 'botToken'; // Bot token used for authentication
// Extract the hash value from the received data $checkHash = $initData["hash"]; unset($initData["hash"]); // Remove the hash key to avoid including it in the verification process
$keyValuePairs = [];
foreach ($initData as $key => $value) { // Combine each key-value pair into "key=value" format $keyValuePairs[] = $key . "=" . $value; }
// Sort key-value pairs alphabetically by key sort($keyValuePairs);
// Create the data string to be hashed $dataCheckString = implode("\n", $keyValuePairs);
// Calculate the secret key using the bot token $secretKey = hash_hmac('sha256', "WebAppData", $botAccessToken, true);
// Generate the hash to compare with the received hash $hash = hash_hmac('sha256', $dataCheckString, $secretKey);
// Now, you can compare $hash with $checkHash to verify data integrity if (hash_equals($hash, $checkHash)) { echo "Data is valid!"; } else { echo "Data is invalid!"; }?>
const crypto = require("crypto");const querystring = require("querystring");
// The initialization data received (replace with actual data)const initData = "auth_date=1234567890&hash=53a0...&query_id=...";const botToken = "botToken";
// Parse initData into key-value pairsconst parsed = querystring.parse(initData);const checkHash = parsed.hash;delete parsed.hash;
// Create data string in sorted key=value\n formatconst dataCheckString = Object.keys(parsed) .sort() .map((key) => `${key}=${parsed[key]}`) .join("\n");
// Compute secret key and hashconst secretKey = crypto .createHmac("sha256", botToken) .update("WebAppData") .digest();
const hash = crypto .createHmac("sha256", secretKey) .update(dataCheckString) .digest("hex");
// Compare hashesif (crypto.timingSafeEqual(Buffer.from(hash), Buffer.from(checkHash))) { console.log("Data is valid!");} else { console.log("Data is invalid!");}
import hmacimport hashlibfrom urllib.parse import parse_qsl
# The initialization data received (replace with actual data)init_data = 'auth_date=1234567890&hash=53a0...&query_id=...'bot_token = 'botToken'
# Parse initData into dictionarydata = dict(parse_qsl(init_data))check_hash = data.pop("hash", None)
# Create sorted key=value string separated by \ndata_check_string = "\n".join( f"{k}={v}" for k, v in sorted(data.items()))
# Compute secret key and hashsecret_key = hmac.new( key=bot_token.encode(), msg=b"WebAppData", digestmod=hashlib.sha256).digest()
valid_hash = hmac.new( key=secret_key, msg=data_check_string.encode(), digestmod=hashlib.sha256).hexdigest()
# Compare hashesif hmac.compare_digest(valid_hash, check_hash): print("Data is valid!")else: print("Data is invalid!")
package main
import ( "crypto/hmac" "crypto/sha256" "encoding/hex" "fmt" "net/url" "sort" "strings")
func main() { initData := "auth_date=1234567890&hash=53a0...&query_id=..." botToken := "botToken"
values, _ := url.ParseQuery(initData) checkHash := values.Get("hash") values.Del("hash")
// Build sorted key=value slice keys := make([]string, 0, len(values)) for k := range values { keys = append(keys, k) } sort.Strings(keys)
var dataCheckString strings.Builder for i, k := range keys { if i > 0 { dataCheckString.WriteString("\n") } dataCheckString.WriteString(k + "=" + values.Get(k)) }
// Compute secret key and hash secretKey := hmac.New(sha256.New, []byte(botToken)) secretKey.Write([]byte("WebAppData")) key := secretKey.Sum(nil)
hash := hmac.New(sha256.New, key) hash.Write([]byte(dataCheckString.String())) computedHash := hex.EncodeToString(hash.Sum(nil))
// Compare hashes if hmac.Equal([]byte(computedHash), []byte(checkHash)) { fmt.Println("Data is valid!") } else { fmt.Println("Data is invalid!") }}
#
Security Considerations- Always validate
initData
on the server-side to prevent spoofing. - Use HTTPS to prevent MITM attacks.
- Ensure that
auth_date
is recent to prevent replay attacks.