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=valueformat. - Generate an HMAC-SHA256 hash using the bot’s access token as the secret key.
- Sort all parameters (except
Token Validation Examples
Use 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 pairs
const parsed = querystring.parse(initData);
const checkHash = parsed.hash;
delete parsed.hash;
// Create data string in sorted key=value\n format
const dataCheckString = Object.keys(parsed)
.sort()
.map((key) => `${key}=${parsed[key]}`)
.join("\n");
// Compute secret key and hash
const secretKey = crypto
.createHmac("sha256", botToken)
.update("WebAppData")
.digest();
const hash = crypto
.createHmac("sha256", secretKey)
.update(dataCheckString)
.digest("hex");
// Compare hashes
if (crypto.timingSafeEqual(Buffer.from(hash), Buffer.from(checkHash))) {
console.log("Data is valid!");
} else {
console.log("Data is invalid!");
}
import hmac
import hashlib
from 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 dictionary
data = dict(parse_qsl(init_data))
check_hash = data.pop("hash", None)
# Create sorted key=value string separated by \n
data_check_string = "\n".join(
f"{k}={v}" for k, v in sorted(data.items())
)
# Compute secret key and hash
secret_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 hashes
if 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
initDataon the server-side to prevent spoofing. - Use HTTPS to prevent MITM attacks.
- Ensure that
auth_dateis recent to prevent replay attacks.