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
Maintainers
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 tokenRequirements
- 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-loginOr with npm/yarn:
npm install expo-telegram-loginRegister the config plugin in app.json
Pass your app's URL scheme to the plugin. It will:
- Add
tgtoLSApplicationQueriesSchemeson iOS (required to detect whether Telegram is installed) - Register
yourapp://tgloginas a URL scheme on iOS (CFBundleURLTypes) - Add an intent-filter for
yourapp://tgloginon Android
{
"expo": {
"scheme": "yourapp",
"plugins": [
["expo-telegram-login", { "callbackScheme": "yourapp" }]
]
}
}If
callbackSchemeis omitted from the plugin options, it falls back to the top-levelschemefield in yourapp.json.
Rebuild
npx expo prebuild
npx expo run:ios
npx expo run:android3. 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=developerand enable Associated Domains Development in iPhone Settings → Developer.Remove
?mode=developerbefore 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)
- Module calls
GET https://oauth.telegram.org/crossappwith PKCE params → receives atg://deep link - Opens Telegram via
UIApplication.open/Intent.ACTION_VIEW - User sees the confirmation dialog inside Telegram and taps Continue
- Telegram redirects to
yourapp://tglogin?code=... - Module intercepts via
RCTOpenURLNotification(iOS) /onNewIntent(Android) - Promise resolves with
{ code, codeVerifier, redirectUri }
Fallback — Web (Telegram not installed)
- Opens
https://oauth.telegram.org/authin anASWebAuthenticationSession(iOS) or Chrome Custom Tab (Android) - User authenticates in the browser
- Telegram redirects to
redirectUri(your backend callback) - Backend 302-redirects to
yourapp://tglogin?code=... - Session intercepts via
callbackURLScheme(iOS) or system URL handling (Android) - Promise resolves with
{ code, codeVerifier, redirectUri }
License
MIT
