JavaScript에서 Telegram WebApp 인증
오늘은 Telegram WebApp Bot의 까다로운 인증 메커니즘에 대해 말씀드리겠습니다. Telegram WebApp 봇이란 무엇입니까? Telegram 내부의 웹사이트에서 WebView를 실행하는 기능일 뿐입니다. 여기에서 자세한 내용을 읽을 수 있습니다 .
여기서 인증이 필요한 이유는 무엇입니까?
귀하의 웹사이트는 Telegram WebView에서만 열려야 합니다. 그렇다면 누군가 브라우저에서 이 작업을 수행한다면 어떻게 될까요? "해커"는 가짜 사용자 데이터, ID 등을 사용할 수 있습니다. Telegram 외부의 사람들로부터 API를 보호해야 합니다.
어떻게?
Telegram은 HMAC(해시 기반 메시지 인증 코드)를 사용합니다. 따라서 웹사이트에서 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단계: 인증 미들웨어 생성
내 프로젝트에서 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 알고리즘과 키 를 사용하여 메시지를 인코딩하는 함수 작성
- 이전 단계의 함수를 사용하여 문자열 구문 분석
- "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 토큰을 한 번 생성하고 쿠키로 설정할 수 있습니다. 인증을 생성하는 더 강력한 방법이라고 생각합니다.

![연결된 목록이란 무엇입니까? [1 부]](https://post.nghiatu.com/assets/images/m/max/724/1*Xokk6XOjWyIGCBujkJsCzQ.jpeg)



































