npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

expo-telegram-login

v1.0.0

Published

Native Telegram Login (OIDC + PKCE) module for Expo — opens the Telegram app directly via the crossapp flow with an ASWebAuthenticationSession / Chrome Custom Tabs fallback

Downloads

133

Readme

expo-telegram-login

Native Telegram Login for Expo apps. Uses Telegram's crossapp flow to open the Telegram app directly — the user taps Confirm in Telegram, and your app receives an authorization code via PKCE-protected OIDC. Falls back to ASWebAuthenticationSession (iOS) or Chrome Custom Tabs (Android) when Telegram is not installed.

How it works

Your App → GET /crossapp → tg:// deep link → Telegram App
                                                    ↓ user confirms
Your App ← yourapp://tglogin?code=... ← Telegram App
    ↓
POST /oidc-endpoint { code, code_verifier, redirect_uri }
    ↓
Your backend exchanges code with Telegram → access token

Requirements

  • Expo SDK ≥ 50
  • iOS 15.1+
  • Android API 24+
  • A Telegram Bot registered via @BotFather

1. BotFather Setup

Open your bot in BotFather and go to Bot Settings → Login Widget:

| Setting | Value | | ----------------- | ------------------------------------------------- | | iOS App | Bundle ID + Apple Team ID | | Android App | Package name + SHA-256 fingerprint | | Redirect URIs | Add your app's custom scheme: yourapp://tglogin |

BotFather will give you:

  • A Client ID (numeric bot ID, e.g. 8417046141)
  • Platform-specific login domain URLs (e.g. https://app<id>-login.tg.dev/) — these are the URIs your backend must accept for token exchange.

2. Installation

npx expo install expo-telegram-login

Or with npm/yarn:

npm install expo-telegram-login

Register the config plugin in app.json

Pass your app's URL scheme to the plugin. It will:

  • Add tg to LSApplicationQueriesSchemes on iOS (required to detect whether Telegram is installed)
  • Register yourapp://tglogin as a URL scheme on iOS (CFBundleURLTypes)
  • Add an intent-filter for yourapp://tglogin on Android
{
  "expo": {
    "scheme": "yourapp",
    "plugins": [
      ["expo-telegram-login", { "callbackScheme": "yourapp" }]
    ]
  }
}

If callbackScheme is omitted from the plugin options, it falls back to the top-level scheme field in your app.json.

Rebuild

npx expo prebuild
npx expo run:ios
npx expo run:android

3. Usage

import { loginAsync } from 'expo-telegram-login';
import { Platform } from 'react-native';

const BOT_CLIENT_ID = '8417046141'; // your bot's numeric ID from BotFather

// Your backend callback URL (302-redirects to yourapp://tglogin?code=...)
const WEB_REDIRECT_URI = 'https://api.yourapp.com/telegram-callback/';

// Telegram-generated domain URL from BotFather (used for OIDC token exchange)
const OIDC_REDIRECT_URI = Platform.select({
  ios: 'https://app<your-ios-id>-login.tg.dev/',
  android: 'https://app<your-android-id>-login.tg.dev/',
})!;

async function handleTelegramLogin() {
  try {
    const result = await loginAsync({
      clientId: BOT_CLIENT_ID,
      redirectUri: WEB_REDIRECT_URI,
      oidcRedirectUri: OIDC_REDIRECT_URI,
      callbackScheme: 'yourapp', // must match your app.json "scheme"
    });

    // Send to your backend
    await fetch('https://api.yourapp.com/auth/telegram/oidc/', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        code: result.code,
        code_verifier: result.codeVerifier,
        redirect_uri: result.redirectUri,
      }),
    });
  } catch (error: any) {
    if (error.code === 'CANCELLED') {
      // User cancelled — do nothing
    } else {
      console.error('Telegram login failed:', error);
    }
  }
}

4. Backend Requirements

Your backend receives { code, code_verifier, redirect_uri } and must exchange the code with Telegram's OIDC token endpoint:

POST https://oauth.telegram.org/auth/token
  grant_type=authorization_code
  &client_id=<BOT_CLIENT_ID>
  &code=<code>
  &code_verifier=<code_verifier>
  &redirect_uri=<redirect_uri>

Telegram returns { id_token, access_token, ... }. Verify the id_token JWT using Telegram's JWKS endpoint, then log the user in.

The redirect_uri your backend receives will be:

  • The Telegram domain URL (https://app<id>-login.tg.dev/) when Telegram app was used
  • The web callback URL (https://api.yourapp.com/telegram-callback/) when the fallback flow was used

Both must be registered in BotFather's redirect allow list.


5. Deep Link Setup

The module delivers the authorization code back to your app via the custom scheme yourapp://tglogin. Expo Router handles this automatically if you have a screen at the tglogin path. If you use Expo Router, add a placeholder screen to prevent an "Unmatched Route" error:

app/tglogin.tsx

import { ActivityIndicator, View } from 'react-native';

export default function TelegramLoginCallback() {
  return (
    <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
      <ActivityIndicator size="large" />
    </View>
  );
}

6. iOS — Universal Links (optional but recommended)

Telegram can also redirect via a Universal Link (https://app<id>-login.tg.dev/) instead of a custom scheme. To enable this, add the domain to Associated Domains in app.json:

{
  "expo": {
    "ios": {
      "associatedDomains": ["applinks:app<your-ios-id>-login.tg.dev"]
    }
  }
}

During development on a physical device, append ?mode=developer and enable Associated Domains Development in iPhone Settings → Developer.

Remove ?mode=developer before submitting to the App Store.


API

loginAsync(options)

Starts the Telegram Login flow. Returns a promise that resolves with the authorization result.

Options

| Option | Type | Required | Default | Description | | ----------------- | -------- | -------- | --------------------- | ------------------------------------------------------------------------------------------ | | clientId | string | ✅ | — | Numeric bot ID from BotFather | | redirectUri | string | ✅ | — | Web fallback redirect URI (your backend callback) | | oidcRedirectUri | string | — | same as redirectUri | Value returned as redirectUri in the result — use the Telegram domain URL from BotFather | | callbackScheme | string | ✅ | — | Your app's URL scheme (the part before ://) — must match scheme in app.json |

Result

| Field | Type | Description | | -------------- | -------- | ------------------------------------------------------------------- | | code | string | PKCE authorization code | | codeVerifier | string | PKCE code verifier — send to backend alongside code | | redirectUri | string | The redirect URI that was used — pass to backend for token exchange |

Errors

| Code | Description | | ------------------- | -------------------------------------------------------------- | | CANCELLED | User dismissed the login sheet or navigated back from Telegram | | INVALID_CLIENT_ID | clientId option was empty or missing | | INVALID_REDIRECT | redirectUri option was empty or missing | | INVALID_CALLBACK_SCHEME | callbackScheme option was empty or missing | | AUTH_ERROR | ASWebAuthenticationSession returned a non-cancellation error | | PARSE_ERROR | Could not extract code from the callback URL | | URL_ERROR | Failed to build the authorization URL | | NO_ACTIVITY | Android: no current activity available |


Flow Details

Primary — Crossapp (Telegram installed)

  1. Module calls GET https://oauth.telegram.org/crossapp with PKCE params → receives a tg:// deep link
  2. Opens Telegram via UIApplication.open / Intent.ACTION_VIEW
  3. User sees the confirmation dialog inside Telegram and taps Continue
  4. Telegram redirects to yourapp://tglogin?code=...
  5. Module intercepts via RCTOpenURLNotification (iOS) / onNewIntent (Android)
  6. Promise resolves with { code, codeVerifier, redirectUri }

Fallback — Web (Telegram not installed)

  1. Opens https://oauth.telegram.org/auth in an ASWebAuthenticationSession (iOS) or Chrome Custom Tab (Android)
  2. User authenticates in the browser
  3. Telegram redirects to redirectUri (your backend callback)
  4. Backend 302-redirects to yourapp://tglogin?code=...
  5. Session intercepts via callbackURLScheme (iOS) or system URL handling (Android)
  6. Promise resolves with { code, codeVerifier, redirectUri }

License

MIT