C# Implementation for Verifying Telegram Bot Authentication Hash

I’m working on authenticating users through the Telegram login widget but running into issues with hash verification. My approach involves extracting parameters from the callback URL, removing the hash value, sorting remaining parameters by key name, and creating a validation string with newline separators.

The process I follow:

  1. Extract the hash parameter and remove it from the collection.
  2. Sort the remaining parameters alphabetically.
  3. Create a verification string using key=value format.
  4. Generate HMAC-SHA256 using the bot token as a secret.
  5. Compare the computed hash with the received hash.

However, the hashes never match, and I can’t figure out what’s wrong.

I also have some questions about the implementation:

  • Should URL-encoded values like photo_url be decoded first?
  • Does the verification string need to be converted to lowercase?

Here’s my current code:

public IActionResult VerifyTelegramAuth([FromQuery] Dictionary<string, string> parameters)
{
    if (!parameters.TryGetValue("hash", out var receivedHash) || string.IsNullOrEmpty(receivedHash))
    {
        return BadRequest("Hash parameter is missing");
    }
    parameters.Remove("hash");
    
    if (parameters.ContainsKey("photo_url"))
    {
        parameters["photo_url"] = WebUtility.UrlDecode(parameters["photo_url"]);
    }
    
    var verificationString = string.Join("\n", parameters.OrderBy(pair => pair.Key)
                                                        .Select(pair => $"{pair.Key}={pair.Value}"));

    var secretKey = ComputeSha256("MyBotToken");

    var computedHash = ComputeHmacSha256(secretKey, Encoding.UTF8.GetBytes(verificationString));

    var computedHashString = string.Concat(computedHash.Select(b => b.ToString("x2")));
    
    if (computedHashString == receivedHash)
    {
        return Ok("Authentication successful");
    }
    else
    {
        return Unauthorized("Authentication failed");
    }
}

private static byte[] ComputeSha256(string input)
{
    using (SHA256 sha256 = SHA256.Create())
    {
        return sha256.ComputeHash(Encoding.UTF8.GetBytes(input));
    }
}

private static byte[] ComputeHmacSha256(byte[] key, byte[] data)
{
    using (var hmac = new HMACSHA256(key))
    {
        return hmac.ComputeHash(data);
    }
}

Sample parameters I’m working with:

{
  "user_id": "12345",
  "name": "TestUser",
  "login": "testuser123",
  "avatar_url": "https%3A%2F%2Ft.me%2Favatar123.jpg",
  "timestamp": "1640995200",
  "hash": "abc123def456"
}

Your secret key generation is probably the culprit. Telegram’s API wants you to use SHA256 of your bot token as the HMAC key, but here’s the gotcha - use the complete token, including any prefix. I kept getting auth failures because I was accidentally trimming whitespace when reading the token from my config file. Also check your parameter filtering - test environments sometimes throw in extra params that shouldn’t be in the hash calculation. Log your verification string before hashing to make sure it matches what Telegram expects. Should be clean key=value pairs separated by newlines, no extra spaces or characters.

Check your timestamp validation - Telegram auth data expires, so old hashes won’t match even with correct data. Also watch for empty or null parameters in your verification string. I had a bug where params came through as empty strings and broke the entire hash calculation.

Had the same issue a few months back - turned out to be my URL decoding approach. You’re only decoding photo_url, but Telegram URL-encodes other parameters too. I fixed it by decoding all query string parameters since browsers naturally URL-encode them. Also check your bot token format for the secret key. Use the raw token string exactly as Telegram gave it - don’t modify it at all. For your questions: yes, decode URL-encoded values before creating the verification string. But no, don’t convert to lowercase - the hash comparison needs to be case-sensitive like Telegram sends it. Log both your computed hash and the received hash to spot the difference. I had a subtle encoding issue that was obvious once I could see them side by side.

This topic was automatically closed 24 hours after the last reply. New replies are no longer allowed.