Skip to content

Authentication

TMA.sh authentication turns Telegram’s initData string into a verified user identity and a signed JWT. No passwords, no OAuth flows — the user is already authenticated by Telegram.

  1. Telegram injects initData into the WebApp context when a user opens your Mini App.
  2. Your app sends initData to TMA.sh via the SDK.
  3. TMA.sh validates the HMAC-SHA256 signature against your bot token, confirming the data came from Telegram and has not been tampered with.
  4. TMA.sh returns a signed JWT containing the user’s Telegram identity.
  5. Your app uses that JWT to authenticate API requests to your own backend or third-party services.

The entire flow is a single function call on the client.

import { createTMA } from '@tma.sh/sdk';
const tma = createTMA({ projectId: 'your-project-id' });
const { user, jwt } = await tma.auth.validate(
window.Telegram.WebApp.initData,
'your-project-id'
);
// user: { telegramId, firstName, lastName, username }
// jwt: signed token (24h expiry)

The returned jwt is a compact JWS signed with your project’s key. Include it as a Bearer token in requests to your API routes or external backends.

Every JWT issued by TMA.sh includes the following claims:

ClaimDescriptionExample
subSubject identifiertg_123456789
telegramIdTelegram user ID123456789
firstNameUser’s first nameAlice
lastNameUser’s last name (may be empty)Smith
usernameTelegram username (may be empty)alicesmith
projectIdTMA.sh project IDproj_abc123
iatIssued at (Unix timestamp)1700000000
expExpiration (Unix timestamp, 24h)1700086400

The sub claim is prefixed with tg_ to avoid collisions when integrating with external auth systems.

The useTelegramAuth hook handles the full auth lifecycle — validation, loading state, and error handling:

import { TMAProvider, useTelegramAuth } from '@tma.sh/sdk/react';
function App() {
return (
<TMAProvider config={{ projectId: 'your-project-id' }}>
<Profile />
</TMAProvider>
);
}
function Profile() {
const { user, jwt, isLoading, error } = useTelegramAuth();
if (isLoading) return <div>Loading...</div>;
if (error) return <div>Auth failed: {error.message}</div>;
return <div>Welcome, {user.firstName}!</div>;
}

Wrap your app in <TMAProvider> once at the root, passing your projectId via the config prop. The provider initializes the SDK and makes auth state available to all child components.

Initialize the TMA provider once in your root layout, then use the getTMAAuth function to access the reactive auth store in any component:

<script>
import { initTMAProvider, getTMAAuth } from '@tma.sh/sdk/svelte';
// Call once in root layout
initTMAProvider({ projectId: 'your-project-id' });
// In components, get the auth store
const auth = getTMAAuth();
</script>
{#if $auth.isLoading}
<p>Loading...</p>
{:else if $auth.user}
<p>Welcome, {$auth.user.firstName}!</p>
{/if}

The store subscribes to auth state changes and updates the UI automatically. Check $auth.user to determine if the user is authenticated — there is no isAuthenticated property.

TMA.sh JWTs work directly with Supabase Row Level Security. Set your project’s JWT secret in the TMA.sh dashboard (Settings > Auth > JWT Secret), then pass the token to the Supabase client:

import { createClient } from '@supabase/supabase-js';
const supabase = createClient(
'https://your-project.supabase.co',
'your-anon-key',
{
global: {
headers: { Authorization: `Bearer ${jwt}` },
},
}
);
// RLS policies can use auth.uid() which returns 'tg_123456789'
const { data } = await supabase
.from('profiles')
.select('*')
.eq('user_id', user.telegramId);

This pattern works with any backend that supports JWT verification — Firebase, Turso, or your own service.

TMA.sh exposes a single JWKS endpoint for custom backend verification:

https://api.tma.sh/.well-known/jwks.json

Use this to verify JWTs from your own server without sharing secrets. Most JWT libraries support JWKS out of the box:

import { createRemoteJWKSet, jwtVerify } from 'jose';
const JWKS = createRemoteJWKSet(
new URL('https://api.tma.sh/.well-known/jwks.json')
);
const { payload } = await jwtVerify(token, JWKS);
// payload.sub === 'tg_123456789'

For API routes deployed on TMA.sh, use the requireUser() middleware instead of manual JWT verification. It extracts the Bearer token, verifies it via JWKS, and injects the user into the Hono context.

See Server Helpers for usage.