Skip to content

Authentication

When Telegram opens your Mini App, it passes signed initData to the WebView. TMA.sh validates this data server-side and returns a JWT you can use to authenticate requests to your own backend or third-party services like Supabase.

  1. Telegram opens your Mini App and injects initData into the WebView
  2. Your app sends initData to the TMA.sh auth endpoint via the SDK
  3. TMA.sh validates the signature against your bot token
  4. A signed JWT is returned containing the Telegram user’s identity

Use the SDK to validate initData and get a JWT:

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'
);
console.log(user.telegramId); // 123456789
console.log(user.firstName); // "Alice"
console.log(user.username); // "alice"

The returned jwt is a signed token you can attach to subsequent requests:

const response = await fetch('https://myapp--api.tma.sh/api/profile', {
headers: {
Authorization: `Bearer ${jwt}`,
},
});

The JWT issued by TMA.sh contains the following claims:

ClaimDescriptionExample
subUnique subject identifiertg_123456789
telegramIdTelegram user ID123456789
firstNameUser’s first nameAlice
lastNameUser’s last name (may be empty)Smith
usernameTelegram username (may be empty)alice
projectIdYour TMA.sh project ID11111111-2222-4333-8444-555555555555
iatIssued at (Unix timestamp)1700000000
expExpires at (24 hours after issuance)1700086400

Protect your API routes with the requireUser() middleware:

import { Hono } from 'hono';
import { requireUser } from '@tma.sh/sdk/server';
const app = new Hono();
app.use('/api/protected/*', requireUser());
app.get('/api/protected/profile', (c) => {
const user = c.get('user');
return c.json({
telegramId: user.telegramId,
username: user.username,
});
});
export default app;

The middleware verifies the JWT signature, checks expiration, and attaches the decoded user to the request context. Unauthorized requests receive a 401 response.

You can forward the TMA JWT to Supabase as a Bearer token from your Mini App:

import { createClient } from '@supabase/supabase-js';
import { createTMA } from '@tma.sh/sdk';
const tma = createTMA({ projectId: 'your-project-id' });
const { jwt } = await tma.auth.validate(
window.Telegram.WebApp.initData,
'your-project-id'
);
const supabase = createClient(
'https://your-project.supabase.co',
'your-anon-key',
{
global: {
headers: { Authorization: `Bearer ${jwt}` },
},
}
);
// Supabase queries now run with the forwarded JWT
const { data } = await supabase
.from('profiles')
.select('*')
.eq('telegram_id', 'tg_123456789');

To enforce RLS with TMA JWTs, configure Supabase auth verification to trust TMA-issued tokens (for example via JWKS/external JWT verification). The public key set is available at https://api.tma.sh/.well-known/jwks.json.

For custom backend verification, TMA.sh exposes a JWKS (JSON Web Key Set) endpoint:

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

This is a global endpoint (not per-project). Use it to verify JWTs in any language or framework that supports JWKS:

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'
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>Error: {error.message}</div>;
return <div>Welcome, {user.firstName}!</div>;
}
<script>
import { initTMAProvider, getTMAAuth } from '@tma.sh/sdk/svelte';
// Call once in root layout
initTMAProvider({ projectId: 'your-project-id' });
// 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}
  • JWTs expire after 24 hours. Re-validate initData to get a fresh token.
  • Never expose your bot token or JWT signing secret in client-side code.
  • Always verify JWTs server-side before trusting user identity.
  • Use HTTPS for all API requests (TMA.sh enforces this by default).