Skip to content

Instantly share code, notes, and snippets.

@rheckart
Created January 27, 2024 14:59
Show Gist options
  • Save rheckart/d3bc1aea0ee2174c7f8e820b93bcb9f4 to your computer and use it in GitHub Desktop.
Save rheckart/d3bc1aea0ee2174c7f8e820b93bcb9f4 to your computer and use it in GitHub Desktop.
C# ASP.NET Middleware Code to Authenticate Telegram initData
using Microsoft.Extensions.Primitives;
using System.Security.Cryptography;
using System.Text;
using System.Web;
using System.Text.Json;
using System.Net;
using Telegram.Bot.Types;
namespace AnimalTrackz.API.Middleware
{
public class TelegramAuthMiddleware
{
private readonly RequestDelegate _next;
private const string TokenKey = "WebAppData";
private readonly byte[]? _secretHmac;
private readonly string _webHookSecretToken;
public TelegramAuthMiddleware(RequestDelegate next, string botToken, string webHookSecretToken)
{
_next = next;
using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(TokenKey));
_secretHmac = hmac.ComputeHash(Encoding.UTF8.GetBytes(botToken));
_webHookSecretToken = webHookSecretToken;
}
public async Task InvokeAsync(HttpContext context)
{
// Webhook
if (context.Request.Headers.TryGetValue("X-Telegram-Bot-Api-Secret-Token", out var sentBotApiSecretToken))
{
if (sentBotApiSecretToken != _webHookSecretToken)
{
context.Response.StatusCode = ((int)HttpStatusCode.Unauthorized);
return;
}
}
else if (context.Request.Headers.TryGetValue("X-Telegram-Bot-InitData", out var initData)) // Website inside Telegram
{
try
{
if (StringValues.IsNullOrEmpty(initData))
{
context.Response.StatusCode = ((int)HttpStatusCode.Unauthorized);
return;
}
var queryStringData = HttpUtility.ParseQueryString(initData.ToString());
var dictData = new SortedDictionary<string, string>(
#pragma warning disable CS8714 // The type cannot be used as type parameter in the generic type or method. Nullability of type argument doesn't match 'notnull' constraint.
queryStringData.AllKeys.ToDictionary(key => key, key => queryStringData[key])!
#pragma warning restore CS8714 // The type cannot be used as type parameter in the generic type or method. Nullability of type argument doesn't match 'notnull' constraint.
);
var telegramAuthDateTimeOffset = DateTimeOffset.FromUnixTimeSeconds(Convert.ToInt64(dictData["auth_date"]));
// auth_date too old check
if (DateTime.UtcNow.Subtract(telegramAuthDateTimeOffset.UtcDateTime).TotalMinutes > 60)
{
context.Response.StatusCode = ((int)HttpStatusCode.RequestTimeout);
return;
}
/*if (!ValidateTelegramHash(dictData))
{
context.Response.StatusCode = ((int)HttpStatusCode.Unauthorized);
return;
}*/
var telegramUser = JsonSerializer.Deserialize<User>(dictData["user"]);
context.Items.Add("TelegramUser", telegramUser);
context.Items.Add("TelegramChatInstance", dictData["chat_instance"]);
context.Items.Add("TelegramChatType", dictData["chat_type"]);
}
catch
{
context.Response.StatusCode = ((int)HttpStatusCode.Unauthorized);
return;
}
}
else
{
context.Response.StatusCode = ((int)HttpStatusCode.Unauthorized);
return;
}
await _next(context);
}
private bool ValidateTelegramHash1(IDictionary<string, string> telegramFields)
{
var hash = telegramFields["hash"];
telegramFields.Remove("hash");
var dataStringBuilder = new StringBuilder();
foreach (var field in telegramFields.Where(field => !string.IsNullOrEmpty(field.Value)))
{
dataStringBuilder.Append(field.Key);
dataStringBuilder.Append('=');
dataStringBuilder.Append(field.Value);
dataStringBuilder.Append('\n');
}
dataStringBuilder.Remove(dataStringBuilder.Length - 1, 1);
using var hmacData = new HMACSHA256(_secretHmac!);
var signature = hmacData.ComputeHash(Encoding.UTF8.GetBytes(dataStringBuilder.ToString()));
var hmacDataHex = Convert.ToHexString(signature).ToLower();
return hmacDataHex == hash;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment