Skip to content

Instantly share code, notes, and snippets.

@draincoder
Last active June 26, 2024 08:12
Show Gist options
  • Save draincoder/f86f4ed8020738ced5f9f4eebea2b2e6 to your computer and use it in GitHub Desktop.
Save draincoder/f86f4ed8020738ced5f9f4eebea2b2e6 to your computer and use it in GitHub Desktop.
Python implementation of Telegram.WebApp.initData validation
import hashlib
import hmac
import json
from dataclasses import dataclass
from operator import itemgetter
from typing import Any
from urllib.parse import parse_qsl
@dataclass(eq=False)
class AuthError(Exception):
status: int = 403
detail: str = "unknown auth error"
@property
def message(self) -> str:
return f"Auth error occurred, detail: {self.detail}"
class WebAppAuth:
def __init__(self, bot_token: str) -> None:
self._bot_token = bot_token
self._secret_key = self._get_secret_key()
def get_user_id(self, init_data: str) -> int:
return int(json.loads(self._validate(init_data)["user"])["id"])
def _get_secret_key(self) -> bytes:
return hmac.new(key=b"WebAppData", msg=self._bot_token.encode(), digestmod=hashlib.sha256).digest()
def _validate(self, init_data: str) -> dict[str, Any]:
try:
parsed_data = dict(parse_qsl(init_data, strict_parsing=True))
except ValueError as err:
raise AuthError(detail="invalid init data") from err
if "hash" not in parsed_data:
raise AuthError(detail="missing hash")
hash_ = parsed_data.pop("hash")
data_check_string = "\n".join(f"{k}={v}" for k, v in sorted(parsed_data.items(), key=itemgetter(0)))
calculated_hash = hmac.new(
key=self._secret_key,
msg=data_check_string.encode(),
digestmod=hashlib.sha256,
).hexdigest()
if calculated_hash != hash_:
raise AuthError(detail="invalid hash")
return parsed_data
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment