Telegramm-WebApp-Authentifizierung auf JavaScript

Dec 09 2022
Heute werde ich über den kniffligen Mechanismus der Authentifizierung im Telegram WebApp Bot berichten. Was ist der Telegram WebApp-Bot? Es ist nur eine Möglichkeit, WebView mit Ihrer Website in Telegram auszuführen.

Heute werde ich über den kniffligen Mechanismus der Authentifizierung im Telegram WebApp Bot berichten. Was ist der Telegram WebApp-Bot? Es ist nur eine Möglichkeit, WebView mit Ihrer Website in Telegram auszuführen. Hier können Sie mehr lesen .

Warum brauchen wir hier auth?

Ihre Website soll nur in Telegram WebView geöffnet werden. Was wäre also, wenn jemand dies in einem Browser tun würde? „Hacker“ können gefälschte Benutzerdaten, IDs usw. verwenden. Wir müssen unsere API vor Personen außerhalb von Telegram schützen.

Wie?

Telegram verwendet HMAC (Hash-based Message Authentication Code). Wenn Sie also das Telegram SDK auf Ihrer Website initialisieren, können Sie diese Daten verwenden, um den Benutzer zu identifizieren. Lassen Sie uns Schritt für Schritt einen Authentifizierungsmechanismus erstellen.

Schritt 1: Daten durch Anfragen weitergeben

Telegram SDK richtet Benutzerdaten im globalen Bereich Ihrer App ein. Es gibt Daten über Benutzer, ihre Smartphones, Farbthemen und mehr. Sie finden es hier:

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;

Schritt 2: Auth-Middleware erstellen

Ich verwende Nest.js in meinem Projekt, aber die Vorgehensweise zum Erstellen von Middleware ist in Express.js und Nest.js fast gleich

Zuerst sollten wir die Middleware mit ein paar Zeilen Code erstellen:

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();
    }
}

Schritt 3: Analysieren der initData

Ich werde den Prozess beschreiben und dann zeige ich Ihnen den Code.

  1. Wir müssen den initData -String parsen
  2. Nehmen Sie das Hash -Feld aus dieser Zeichenfolge und bewahren Sie es für die Zukunft auf
  3. Sortieren Sie die restlichen Felder in alphabetischer Reihenfolge
  4. Verbinden Sie diese Felder mit dem Zeilenumbruch (\n). Wieso den? Nur weil! Telegram will es!

Schauen wir uns den Code an:

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'),
    },
  };
}

Dies ist das letzte Kapitel unserer Reise. Wir müssen initData mit der Funktion aus dem vorherigen Schritt und ein wenig Kryptographie parsen.

Wir sollten diesen Weg gehen:

  1. Schreiben Sie eine Funktion zum Codieren von Nachrichten mit dem sh256- Algorithmus und einem Schlüssel
  2. Analysieren Sie die Zeichenfolge mit der Funktion aus dem vorherigen Schritt
  3. Erstellen Sie einen geheimen Schlüssel, indem Sie das Telegram Bot Token mit dem Schlüssel „WebAppData“ codieren
  4. Erstellen Sie einen Validierungshash, indem Sie dataCheckString aus dem vorherigen Stamm mit einem geheimen Schlüssel codieren
  5. Vergleichen Sie den Validierungshash mit dem Hash von 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;
}

  1. Sie können Caching hinzufügen, da Kryptografie für einen Prozessor eine ziemlich komplizierte Sache ist. Sie können also Redis oder sogar In-Memory-Cache verwenden, um beispielsweise eine initData-Zeichenfolge wie einen Schlüssel und userData JSON als Wert zu speichern
  2. Sie können Ihr eigenes JWT-Token einmalig generieren, nachdem Sie initData validiert haben, und Sie können es in Cookies setzen. Ich denke, es ist eine leistungsfähigere Methode, um eine Authentifizierung zu erstellen.