Releases: vcode-sh/better-auth-telegram
v1.5.0
What's new
loginWidget option (#16)
loginWidget?: boolean (default true). Set to false to disable Login Widget endpoints and omit Telegram-specific schema fields. OIDC-only setups no longer get 5 unused database columns cluttering their schema.
telegram({
botToken: "...",
botUsername: "...",
loginWidget: false,
oidc: { enabled: true, clientSecret: "..." },
})Zero breaking changes — undefined defaults to true.
Modular refactoring
src/index.ts (747 LOC monolith) split into 6 focused modules. Same public API, same behaviour, now actually maintainable.
| File | Purpose |
|---|---|
plugin-config.ts |
Config parsing + validation |
schema.ts |
Conditional user/account fields |
config-endpoint.ts |
GET /telegram/config |
widget-endpoints.ts |
signIn, link, unlink |
miniapp-endpoints.ts |
Mini App signIn, validate |
index.ts |
Thin orchestrator (~85 LOC) |
Tests
261 → 329 tests. 100% statement/line/function coverage. Every branch in every extracted module exercised.
Also
loginWidgetEnabledin config response- Rate limits now conditional (only registered when the corresponding flow is active)
- Fixed incorrect cross-provider comments in endpoint handlers
npm install better-auth-telegram@1.5.0
Full Changelog: v1.4.0...v1.5.0
v1.4.0
OIDC actually works now
Full OAuth 2.0 Authorization Code flow with PKCE — tested end-to-end, consent screen to dashboard. The whole thing.
Fixed
- OIDC
invalid_clientresolved (#11) — The Client Secret is NOT the bot token. BotFather provides a separate secret via Bot Settings > Web Login. Removed non-standardoriginandbot_idparams from the auth URL. Clean standard OIDC now. - OIDC cross-domain redirect (#12) —
originwas derived from the backendredirectURI, breaking setups where frontend and backend live on different domains. Telegram redirected toapi.example.com/#tgAuthResult=...instead ofredirect_uri?code=.... Removedoriginentirely — Telegram uses the standardredirect_urinow. - Login Widget / Mini App P2002 crash (#13) —
/telegram/signinand/telegram/miniapp/signincrashed withUnique constraint failed on emailwhen the user already existed via OIDC. Now checks for existing users bytelegramIdbefore attempting creation. Links the account instead of creating a duplicate.
Added
oidc.clientSecretoption — Pass the Client Secret from BotFather's Web Login settings. Falls back to bot token with a warning if omitted, but OIDC won't work without the proper secret.- Step-by-step OIDC setup docs — Including the undocumented BotFather "remove URL, reopen, switch to OpenID Connect Login" ritual.
Changed
- Test count: 225 → 235. Nine new tests for cross-provider account linking and auth URL regression guards. All verified as failing against v1.3.x.
Migration from v1.3.x
- OIDC users: Add
oidc.clientSecretfrom BotFather's Web Login settings (Bot Settings > Web Login). Register your Allowed URLs and Redirect URL there. See OIDC Prerequisites. - Login Widget and Mini App flows are unaffected.
Full changelog: CHANGELOG.md
Full Changelog: v1.3.3...v1.4.0
v1.3.3
Fixed
bot_idactually ships this time (#11) — The v1.3.2 source had thebot_idfix but the npm dist was built before the change landed. The published package sentadditionalParams: { origin }instead ofadditionalParams: { origin, bot_id: botId }. This release rebuilds from the correct source. If you're on 1.3.2 and using OIDC, upgrade.
Added
- OIDC prerequisites documentation — Telegram's OIDC infrastructure is live (
oauth.telegram.org/.well-known/openid-configuration, JWKS, RS256 JWTs) but bot registration isn't publicly documented. Without it, the token endpoint returnsinvalid_clientand the auth endpoint falls back to Login Widget redirects (#tgAuthResulthash fragment instead of?code=authorization code). README now documents this:/setpublickeyvia @Botfather,/setdomain, and the fact that additional undocumented steps may be required. Troubleshooting section updated with OIDC-specific guidance.
Changed
- Test app restructured — Login Widget isolated to
/widgetpage, Mini App SDK loaded dynamically only on/miniapp, home page is now a hub with links. Prevents cross-flow interference during testing.
Full changelog: CHANGELOG.md
Full Changelog: v1.3.2...v1.3.3
v1.3.2
Full Changelog: v1.3.1...v1.3.2
v1.3.1
Full Changelog: v1.3.0...v1.3.1
v1.3.0
Full Changelog: v1.2.0...v1.3.0
v1.2.0
What's New
Fixed
- Residual TS2532 errors in OIDC tests —
plugin.init!(mockCtx)could returnvoidbecause theinithook is conditionally spread. Added non-null assertions. Three fewer red squiggles.
Added
- Telegram test server support (
testModeoption) — settestMode: trueand your bot talks to Telegram's test environment DCs instead of production. HMAC verification is token-agnostic, so zero crypto changes needed. Logs a warning when combined with OIDC becauseoauth.telegram.orghas no documented test variant. BetterAuthPluginRegistrymodule augmentation — declares thetelegramplugin in Better Auth's plugin registry soctx.context.getPlugin("telegram")andctx.context.hasPlugin("telegram")are fully typed. Type-only, zero runtime impact.
Changed
GET /telegram/confignow returnstestModeboolean alongsidebotUsername,miniAppEnabled, andoidcEnabled.- Test count: 173 → 219 tests. 100% coverage across statements, branches, functions, and lines.
Migration from v1.1.0
No breaking changes. testMode is opt-in (default false). BetterAuthPluginRegistry module augmentation is type-only — zero runtime impact. Config endpoint now returns testMode boolean. Your existing code doesn't care.
Full Changelog: https://github.com/vcode-sh/better-auth-telegram/blob/main/CHANGELOG.md
Full Changelog: v1.1.0...v1.2.0
v1.1.0: Better Auth 1.5.0 Compatibility
Better Auth 1.5.0 Compatibility
The type mismatch era is over. #11 reported that better-auth-telegram exploded with type errors when used with better-auth@1.5.0. Turns out Better Auth 1.5 changed $ERROR_CODES from Record<string, string> to Record<string, RawError>. Fair enough.
What Changed
- Error codes migrated to
defineErrorCodes()—ERROR_CODESnow usesdefineErrorCodes()from@better-auth/core/utils/error-codes. Each error code is a properRawErrorobject ({ code, message }) instead of a plain string. - All
APIErrorthrows migrated toAPIError.from()— 14 throw sites updated fromthrow new APIError("STATUS", { message })tothrow APIError.from("STATUS", ERROR_CODES.X). Same behaviour, new API. - Removed
(ctx: any)on init hook — TypeScript now infersAuthContextfrom the plugin type instead of pretending everything isany. @better-auth/coreadded to tsup externals —defineErrorCodesimport resolved at runtime, not bundled.
Breaking Changes
- Peer dependency bumped to
better-auth@^1.5.0— if you're on 1.4.x, upgrade first. This is the minimum that satisfies the newBetterAuthPlugininterface.
Fixed
- Type mismatch with
better-auth@1.5.0(#11) — thanks to @flxxxxddd and @RainyPixel for reporting.
Verification
tsc --noEmit— 0 errorsvitest run— 173/173 tests passedtsupbuild — clean ESM + CJS + declarationsultracite check— 0 issues
Full Changelog: v1.0.0...v1.1.0
Full Changelog: v1.0.0...v1.1.0
v1.0.0: Telegram OIDC Support
🔐 Telegram OIDC (OpenID Connect) Support
Telegram finally joined the OAuth federation with Bot API 9.5, and we're here for it. Login Widget. Mini Apps. OIDC. The whole circus is now complete.
What's New
- Telegram OIDC authentication — Standard OAuth 2.0 Authorization Code flow with PKCE via
oauth.telegram.org. Proper grown-up auth instead of widget callbacks. - Phone number access — The
phonescope gives you what the Login Widget never could. SetrequestPhone: trueand stop guessing. - RS256 JWT verification — ID tokens verified against Telegram's JWKS endpoint. Keys fetched and matched by
kid— no hardcoded secrets, no trust-me-bro validation. - Zero custom endpoints — Injects a
telegram-oidcprovider into Better Auth's social login system via theinithook. Uses standard/sign-in/socialand/callback/telegram-oidcroutes. - New
telegramPhoneNumberfield — Added to the user schema, populated via OIDC phone scope. - 173 tests — 56 new OIDC tests. 100% coverage on
oidc.ts. If it breaks, roast me on X.
Setup
// Server
telegram({
botToken: process.env.TELEGRAM_BOT_TOKEN!,
botUsername: "your_bot_username",
oidc: {
enabled: true,
requestPhone: true,
},
});
// Client
await authClient.signInWithTelegramOIDC({
callbackURL: "/dashboard",
});Breaking Changes
None. OIDC is opt-in (oidc.enabled: false by default). All existing Login Widget and Mini App flows are untouched.
New Configuration Options
| Option | Default | Description |
|---|---|---|
oidc.enabled |
false |
Enable Telegram OIDC flow |
oidc.scopes |
["openid", "profile"] |
OIDC scopes to request |
oidc.requestPhone |
false |
Request phone number |
oidc.requestBotAccess |
false |
Request bot access |
oidc.mapOIDCProfileToUser |
— | Custom OIDC claims mapper |
Database Migration
Add telegramPhoneNumber to your user table:
model User {
// ... existing fields
telegramPhoneNumber String?
}Links
Full Changelog: v0.4.0...v1.0.0
What's Changed
- chore(deps-dev): bump happy-dom from 20.6.3 to 20.7.0 by @dependabot[bot] in #5
- chore(deps): bump actions/checkout from 4 to 6 by @dependabot[bot] in #7
- chore(deps-dev): bump better-auth from 1.4.18 to 1.4.19 in the dev-dependencies group by @dependabot[bot] in #8
New Contributors
- @dependabot[bot] made their first contribution in #5
Full Changelog: v0.4.0...v1.0.0
v0.4.0
Full Changelog: v0.3.2...v0.4.0