useAuth
完整的 Telegram 认证流程封装,支持前端验证和后端认证。
导入
tsx
import { useAuth } from '@xcloud/ui-telegram'示例
基础使用
自动认证模式:
tsx
import { useAuth } from '@xcloud/ui-telegram'
function App() {
const { user, isAuthenticated, isLoading } = useAuth({
endpoint: '/api/auth/telegram',
autoAuth: true,
})
if (isLoading) return <div>Authenticating...</div>
if (!isAuthenticated) return <div>Not authenticated</div>
return <div>Welcome, {user?.firstName}!</div>
}手动认证
用户主动触发登录:
tsx
import { useAuth } from '@xcloud/ui-telegram'
function LoginPage() {
const { authenticate, isLoading, error } = useAuth()
const handleLogin = async () => {
try {
await authenticate('/api/auth/telegram')
} catch (err) {
console.error('Login failed:', err)
}
}
return (
<div>
<button onClick={handleLogin} disabled={isLoading}>
{isLoading ? 'Logging in...' : 'Login with Telegram'}
</button>
{error && <p className="error">{error.message}</p>}
</div>
)
}使用回调
处理认证成功和失败:
tsx
import { useAuth } from '@xcloud/ui-telegram'
import { useNavigate } from 'react-router-dom'
function App() {
const navigate = useNavigate()
const { user, token } = useAuth({
endpoint: '/api/auth/telegram',
autoAuth: true,
onSuccess: (user, token) => {
console.log('Logged in:', user.firstName)
// 保存 token
if (token) {
localStorage.setItem('auth_token', token)
}
// 跳转到首页
navigate('/home')
},
onError: (error) => {
console.error('Auth failed:', error)
// 显示错误提示
toast.error('Authentication failed')
},
})
return <div>{/* Your app */}</div>
}仅前端验证
不使用后端,仅使用 Telegram 提供的数据:
tsx
import { useAuth } from '@xcloud/ui-telegram'
function App() {
// 不传 endpoint,仅使用 Telegram 提供的用户数据
const { user, isAuthenticated } = useAuth({ autoAuth: true })
// user 数据来自 Telegram,已经过 Telegram 签名验证
return isAuthenticated ? (
<div>Hello, {user?.firstName}</div>
) : (
<div>Not in Telegram</div>
)
}登出功能
tsx
import { useAuth } from '@xcloud/ui-telegram'
function Header() {
const { user, isAuthenticated, logout } = useAuth({
endpoint: '/api/auth/telegram',
autoAuth: true,
})
const handleLogout = () => {
logout()
// 清除本地存储的 token
localStorage.removeItem('auth_token')
// 重定向到登录页
window.location.href = '/login'
}
if (!isAuthenticated) return null
return (
<header>
<span>Welcome, {user?.firstName}</span>
<button onClick={handleLogout}>Logout</button>
</header>
)
}Hook API
参数 (AuthOptions)
typescript
interface AuthOptions {
// 后端认证接口地址
endpoint?: string
// 自动认证(组件挂载时自动调用认证)
autoAuth?: boolean // default: false
// 认证成功回调
onSuccess?: (user: User, token?: string) => void
// 认证失败回调
onError?: (error: Error) => void
// 自定义请求头
headers?: Record<string, string>
}返回值 (AuthState & Methods)
typescript
{
// 状态
user: User | null // 当前认证的用户
isAuthenticated: boolean // 是否已认证
isLoading: boolean // 是否正在认证
error: Error | null // 认证错误
token: string | null // 后端返回的 token
// 方法
authenticate: (endpoint?: string) => Promise<void> // 手动认证
logout: () => void // 登出
// 额外信息
initData: string | undefined // 原始 initData
startParam: string | undefined // 启动参数
}认证流程
1. 前端收集数据
typescript
// Hook 自动从 Telegram 获取
const initData = retrieveLaunchParams().initDataRaw
const platform = retrieveLaunchParams().platform2. 发送到后端
typescript
fetch('/api/auth/telegram', {
method: 'POST',
headers: {
'Authorization': `tma ${initDataRaw}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
initData: initDataRaw,
platform: platform,
}),
})3. 后端验证
后端需要验证签名和有效期:
typescript
// Node.js 示例
import crypto from 'crypto'
function verifyTelegramAuth(initDataRaw: string, botToken: string) {
const parsed = new URLSearchParams(initDataRaw)
const hash = parsed.get('hash')
if (!hash) return false
parsed.delete('hash')
// 创建验证字符串
const dataCheckString = Array.from(parsed.entries())
.sort(([a], [b]) => a.localeCompare(b))
.map(([k, v]) => `${k}=${v}`)
.join('\n')
// 生成密钥
const secretKey = crypto
.createHmac('sha256', 'WebAppData')
.update(botToken)
.digest()
// 计算哈希
const computedHash = crypto
.createHmac('sha256', secretKey)
.update(dataCheckString)
.digest('hex')
return computedHash === hash
}
// 验证有效期 (建议 1 小时)
const authDate = parseInt(parsed.get('auth_date') || '0')
const now = Math.floor(Date.now() / 1000)
if (now - authDate > 3600) {
throw new Error('Init data expired')
}4. 返回 Token
typescript
// 后端返回格式
{
"success": true,
"token": "eyJhbGciOiJIUzI1NiIs...", // JWT token
"user": {
"id": 123456,
"firstName": "John",
"username": "johndoe"
}
}常见场景
受保护的路由
tsx
import { useAuth } from '@xcloud/ui-telegram'
import { Navigate } from 'react-router-dom'
function ProtectedRoute({ children }) {
const { isAuthenticated, isLoading } = useAuth({
endpoint: '/api/auth',
autoAuth: true,
})
if (isLoading) {
return <div>Loading...</div>
}
if (!isAuthenticated) {
return <Navigate to="/login" replace />
}
return children
}
// 使用
<Route path="/dashboard" element={
<ProtectedRoute>
<Dashboard />
</ProtectedRoute>
} />API 请求携带 Token
tsx
import { useAuth } from '@xcloud/ui-telegram'
import { useEffect } from 'react'
function useApiClient() {
const { token } = useAuth()
return {
fetch: (url: string, options = {}) => {
return fetch(url, {
...options,
headers: {
...options.headers,
'Authorization': `Bearer ${token}`,
},
})
},
}
}
// 使用
function DataComponent() {
const api = useApiClient()
const [data, setData] = useState(null)
useEffect(() => {
api.fetch('/api/user/profile')
.then(res => res.json())
.then(setData)
}, [])
return <div>{JSON.stringify(data)}</div>
}Token 刷新
tsx
import { useAuth } from '@xcloud/ui-telegram'
import { useEffect } from 'react'
function TokenRefresher() {
const { token, authenticate } = useAuth()
useEffect(() => {
if (!token) return
// 每 50 分钟刷新一次 token
const interval = setInterval(() => {
authenticate('/api/auth/refresh')
}, 50 * 60 * 1000)
return () => clearInterval(interval)
}, [token, authenticate])
return null
}多端点支持
tsx
import { useAuth } from '@xcloud/ui-telegram'
import { useState } from 'react'
function MultiEndpointAuth() {
const [activeEndpoint, setActiveEndpoint] = useState('production')
const endpoints = {
production: 'https://api.example.com/auth',
staging: 'https://staging-api.example.com/auth',
development: 'http://localhost:3000/auth',
}
const { authenticate, isAuthenticated } = useAuth()
const handleLogin = (env: string) => {
setActiveEndpoint(env)
authenticate(endpoints[env])
}
return (
<div>
<button onClick={() => handleLogin('production')}>Production</button>
<button onClick={() => handleLogin('staging')}>Staging</button>
<button onClick={() => handleLogin('development')}>Development</button>
</div>
)
}错误处理
tsx
import { useAuth } from '@xcloud/ui-telegram'
import { useState } from 'react'
function LoginWithErrorHandling() {
const { authenticate, error, isLoading } = useAuth()
const [customError, setCustomError] = useState<string | null>(null)
const handleLogin = async () => {
setCustomError(null)
try {
await authenticate('/api/auth/telegram')
} catch (err) {
if (err instanceof Error) {
if (err.message.includes('Network')) {
setCustomError('Network error, please check your connection')
} else if (err.message.includes('401')) {
setCustomError('Invalid credentials')
} else {
setCustomError('An unexpected error occurred')
}
}
}
}
return (
<div>
<button onClick={handleLogin} disabled={isLoading}>
Login
</button>
{(error || customError) && (
<div className="error">
{customError || error?.message}
</div>
)}
</div>
)
}注意事项
- 必须在 TelegramProvider 中使用: 确保应用被 TelegramProvider 包裹
- 后端验证必需: 永远不要仅信任客户端数据,必须在后端验证签名
- Token 安全: 不要将 token 存储在不安全的地方
- 有效期检查: 后端应该检查
auth_date,建议有效期为 1 小时 - HTTPS: 生产环境必须使用 HTTPS
- 错误处理: 始终处理认证失败的情况
最佳实践
完整的认证系统
tsx
// AuthProvider.tsx
import { useAuth } from '@xcloud/ui-telegram'
import { createContext, useContext } from 'react'
const AuthContext = createContext(null)
export function AuthProvider({ children }) {
const auth = useAuth({
endpoint: '/api/auth/telegram',
autoAuth: true,
onSuccess: (user, token) => {
// 保存到 localStorage
if (token) {
localStorage.setItem('token', token)
}
// 发送到分析
analytics.identify(user.id, {
name: user.firstName,
username: user.username,
})
},
onError: (error) => {
// 日志记录
console.error('Auth error:', error)
// 清除旧数据
localStorage.removeItem('token')
},
})
return (
<AuthContext.Provider value={auth}>
{children}
</AuthContext.Provider>
)
}
export const useAuthContext = () => useContext(AuthContext)
// App.tsx
import { AuthProvider } from './AuthProvider'
function App() {
return (
<TelegramProvider>
<AuthProvider>
<Routes />
</AuthProvider>
</TelegramProvider>
)
}相关链接
- useInitData - 获取初始化数据
- TelegramProvider - Telegram 环境提供者
- Telegram Authentication 文档