JavaScript での Telegram WebApp 認証
Dec 09 2022
今日は、Telegram WebApp Bot の認証のトリッキーなメカニズムについて説明します。Telegram WebApp ボットとは? Telegram 内の Web サイトで WebView を実行する機能です。
今日は、Telegram WebApp Bot の認証のトリッキーなメカニズムについて説明します。Telegram WebApp ボットとは? Telegram 内の Web サイトで WebView を実行する機能です。詳しくはこちらをご覧ください。
なぜ認証が必要なのですか?
Web サイトは、Telegram WebView でのみ開く必要があります。では、誰かがブラウザでこれを行うとしたらどうでしょうか? 「ハッカー」は、偽のユーザーのデータ、ID などを使用できます。Telegram 以外の人から API を保護する必要があります。
どのように?
Telegram は HMAC (ハッシュベースのメッセージ認証コード) を使用します。したがって、 Web サイトでTelegram SDKを初期化すると、そのデータを使用してユーザーを識別できるようになります。認証メカニズムを段階的に作成しましょう。
ステップ 1: リクエストを介してデータを渡す
Telegram SDK は、ユーザーのデータをアプリのグローバル スコープに設定します。ユーザー、スマートフォン、カラー テーマなどに関するデータがあります。ここで見つけることができます:
window.Telegram.WebApp
const { initData } = window.Telegram.WebApp
auth_date=<auth_date>&query_id=<query_id>&user=<user>&hash=<hash>
axios.defaults.headers.common['Telegram-Data'] = window?.Telegram?.WebApp?.initData;
ステップ 2: Auth ミドルウェアの作成
私のプロジェクトではNest.jsを使っていますが、ミドルウェアの作り方はExpress.jsとNest.jsでほぼ同じです。
まず、数行のコードでミドルウェアを作成する必要があります。
export function telegramAuthMiddleware(req, res, next) {
// take initData from headers
const iniData = req.headers[
'telegram-data'
];
// use our helpers (see bellow) to validate string
// and get user from it
const user = checkAuthorization(iniData);
// add uses to the request "context" for the future
if (user) {
req.user = user;
next();
// or if the validation is failed response 401
} else {
res.writeHead(401, { 'content-type': 'application/json' });
res.write('unauthorized');
res.end();
}
}
ステップ 3: initData の解析
プロセスを説明してから、コードを示します。
- initData文字列を解析する必要があります
- その文字列からハッシュフィールドを取得し、将来のために保持します。
- 残りのフィールドをアルファベット順に並べ替える
- 改行 (\n) を使用してこれらのフィールドを結合します。なんで?という理由だけで!テレグラムはそれを望んでいます!
コードを見てみましょう:
function parseAuthString(iniData) {
// parse string to get params
const searchParams = new URLSearchParams(iniData);
// take the hash and remove it from params list
const hash = searchParams.get('hash');
searchParams.delete('hash');
// sort params
const restKeys = Array.from(searchParams.entries());
restKeys.sort(([aKey, aValue], [bKey, bValue]) => aKey.localeCompare(bKey));
// and join it with \n
const dataCheckString = restKeys.map(([n, v]) => `${n}=${v}`).join('\n');
return {
dataCheckString,
hash,
// get metaData from params
metaData: {
user: JSON.parse(searchParams.get('user')),
auth_date: searchParams.get('auth_date'),
query_id: searchParams.get('query_id'),
},
};
}
これが私たちの旅の最後の章です。前のステップの関数と少しの暗号化を使用して、initDataを解析する必要があります。
次のパスに従う必要があります。
- sh256アルゴリズムとキーを使用してメッセージをエンコードする関数を作成する
- 前のステップの関数を使用して文字列を解析します
- Telegram Bot Token を「WebAppData」キーでエンコードして秘密鍵を作成する
- 前のステムの dataCheckString を秘密鍵でエンコードして検証ハッシュを作成する
- 検証ハッシュをinitDataからのハッシュと比較する
const crypto = require('crypto')
const WEB_APP_DATA_CONST = "WebAppData"
const TELEGRAM_BOT_TOKEN = "so secret token!!"
// encoding message with key
// we need two types of representation here: Buffer and Hex
function encodeHmac(message, key, repr=undefined) {
return crypto.createHmac('sha256', key).update(message).digest(repr);
}
function checkAuthorization(iniData){
// parsing the iniData sting
const authTelegramData = parseAuthString(iniData);
// creating the secret key and keep it as a Buffer (important!)
const secretKey = encodeHmac(
TELEGRAM_BOT_TOKEN,
WEB_APP_DATA_CONST,
);
// creating the validation key (and transform it to HEX)
const validationKey = encodeHmac(
authTelegramData.dataCheckString,
secretKey,
'hex',
);
// the final step - comparing and returning
if (validationKey === authTelegramData.hash) {
return authTelegramData.metaData.user;
}
return null;
}
const crypto = require('crypto')
const WEB_APP_DATA_CONST = "WebAppData"
const TELEGRAM_BOT_TOKEN = "so secret token!!"
export function telegramAuthMiddleware(req, res, next) {
// take initData from headers
const iniData = req.headers[
'telegram-data'
];
// use our helpers (see bellow) to validate string
// and get user from it
const user = checkAuthorization(iniData);
// add uses to the request "context" for the future
if (user) {
req.user = user;
next();
// or if the validation is failed response 401
} else {
res.writeHead(401, { 'content-type': 'application/json' });
res.write('unauthorized');
res.end();
}
}
function parseAuthString(iniData) {
// parse string to get params
const searchParams = new URLSearchParams(iniData);
// take the hash and remove it from params list
const hash = searchParams.get('hash');
searchParams.delete('hash');
// sort params
const restKeys = Array.from(searchParams.entries());
restKeys.sort(([aKey, aValue], [bKey, bValue]) => aKey.localeCompare(bKey));
// and join it with \n
const dataCheckString = restKeys.map(([n, v]) => `${n}=${v}`).join('\n');
return {
dataCheckString,
hash,
// get metaData from params
metaData: {
user: JSON.parse(searchParams.get('user')),
auth_date: searchParams.get('auth_date'),
query_id: searchParams.get('query_id'),
},
};
}
// encoding message with key
// we need two types of representation here: Buffer and Hex
function encodeHmac(message, key, repr=undefined) {
return crypto.createHmac('sha256', key).update(message).digest(repr);
}
function checkAuthorization(iniData){
// parsing the iniData sting
const authTelegramData = parseAuthString(iniData);
// creating the secret key and keep it as a Buffer (important!)
const secretKey = encodeHmac(
TELEGRAM_BOT_TOKEN,
WEB_APP_DATA_CONST,
);
// creating the validation key (and transform it to HEX)
const validationKey = encodeHmac(
authTelegramData.dataCheckString,
secretKey,
'hex',
);
// the final step - comparing and returning
if (validationKey === authTelegramData.hash) {
return authTelegramData.metaData.user;
}
return null;
}
- 暗号化はプロセッサにとって非常に複雑であるため、キャッシングを追加できます。そのため、Redis またはインメモリ キャッシュを使用して、キーのような initData 文字列と値としての userData JSON を保持できます。
- initData を検証した後、独自の JWT トークンを生成し、それを Cookie に設定できます。認証を作成するためのより強力な方法だと思います。

![とにかく、リンクリストとは何ですか?[パート1]](https://post.nghiatu.com/assets/images/m/max/724/1*Xokk6XOjWyIGCBujkJsCzQ.jpeg)



































