Telegram Login with Node.js

// development

If you’re writing a web app and you want to let your users login via Telegram, there are a couple of ways to do it:

  1. Telegram Login Widget gives you a “Log in with Telegram” button which triggers a pop-up for the OAuth flow. Then, your users will receive a message from Telegram to either accept or decline the authorisation request.
  2. Seamless Web Bots allows you to login by tapping an inline keyboard button from a bot. This means they can both login and launch your web app with a single button, right from within the Telegram app.

In this article we’ll be going through both methods, and as always, you can dive into the code in the example repo if you’d prefer to.


Outline:


Getting ready

Setting your domain

Before we go any further, you’ll first need to set the domain for your bot with @BotFather. If you’re testing on your local machine, you’ll need to use ngrok to expose your localhost server to the internet and obtain a subdomain that you can then set for your bot.

Telegram Login Widget

Callback function

The simplest way to enable login via Telegram is to use the widget script in your HTML page.

<script async src="https://telegram.org/js/telegram-widget.js?8" 
  data-telegram-login="your_bot_name" 
  data-size="large" 
  data-onauth="onTelegramAuth(user)"
  data-request-access="write">
</script>

<script type="text/javascript">
  function onTelegramAuth(user) {
    alert('Logged in as ' + user.first_name + ' ' + user.last_name + ' (' + user.id + (user.username ? ', @' + user.username : '') + ')');
  }
</script>

And there you go. You don’t need any backend to handle this flow; it’s entirely on Telegram to handle user authentication. Once it is successful, you can use your callback function on the frontend to grab the user’s data.

Redirect URL

If you’d like to receive the data on a backend server, like an EC2 instance or a Digital Ocean droplet, use data-auth-url instead of data-onauth in the HTML widget code.

<script async src="https://telegram.org/js/telegram-widget.js?8" 
  data-telegram-login="your_bot_name=" 
  data-size="large"
  data-auth-url="https://your-domain.com/login
  data-request-access="write"></script>

And in your server, set up a GET route as follows:

// Express.js
const express = require('express');
const app = express();

app.get('/login', (req, res) => {
  // you'll get your user's data in req.query
  console.log(req.query);
})

// remember to setup a reverse proxy in your web server so your express app can be reached
app.listen(9999, () => { console.log("Server started on port 9999") });

What you decide to with the data from here is entirely up to you. In most cases, you’d probably want to create a session for your user and redirect them to a protected page on your app.

Seamless Web Bots

Now, the final method, which is also my favourite. It’s a lot easier to achieve this when you’re writing a SPA and don’t want to introduce any third-party libraries or inline javascript into your code.

Bot code

Let’s reuse the Express example above and add some code for our bot:

// Express.js + Slimbot
const express = require('express');
const app = express();

const Slimbot = require('slimbot');
const slimbot = new Slimbot('your-bot-token');

slimbot.on('message', message => {
  if (message.text === "/login") {

    let optionalParams = {
      parse_mode: 'Markdown',
      reply_markup: JSON.stringify({
        inline_keyboard: [[
          { text: 'Login',
            login_url: {
              url: 'https://your-domain.com/login'
            }
          }
        ]]
      })
    };

    slimbot.sendMessage(message.chat.id, 'Click this button to login!', optionalParams);
  } else if (message.text === "/start") {
    slimbot.sendMessage(message.chat.id, 'Click /login or type it into the chat to begin login!');
  }
});

app.get('/login', (req, res) => {
  console.log(req.query);
})

app.listen(9999, () => { console.log("Server started on port 9999") });

Now, all your user has to do is to begin a conversation with your bot, and either tap on /login or type it into the chat and you can handle the rest.

Verification

I’d strongly recommend you to verify the authentication and integrity of the data you receive on the backend. The documentation can be quite intimidating if you’re new to this, so let’s walk through it:

app.get('/login', (req, res) => {
  // Basically, you want a function that checks the signature of the incoming data, and deal with it accordingly
  if (checkSignature(req.query)) {
    // data is authenticated
    // create session, redirect user etc.
  } else {
    // data is not authenticated
  }
})
// We'll destructure req.query to make our code clearer
const checkSignature = ({ hash, ...userData }) => {
  // create a hash of a secret that both you and Telegram know. In this case, it is your bot token
  const secretKey = createHash('sha256')
  .update(CONFIG.BOT_TOKEN)
  .digest();

  // this is the data to be authenticated i.e. telegram user id, first_name, last_name etc.
  const dataCheckString = Object.keys(userData)
  .sort()
  .map(key => (`${key}=${userData[key]}`))
  .join('\n');

  // run a cryptographic hash function over the data to be authenticated and the secret
  const hmac = createHmac('sha256', secretKey)
  .update(dataCheckString)
  .digest('hex');

  // compare the hash that you calculate on your side (hmac) with what Telegram sends you (hash) and return the result
  return hmac === hash;
}

And that’s all! I hope this has been useful for you.