Authentication & Security
Lucky Funatic uses different authentication mechanisms depending on who's making the request: players, minigame clients, the Funtico Platform, or internal services.
Authentication Methods
| Consumer | Method | Details |
|---|---|---|
| Telegram Mini App (players) | JWT | Issued after Telegram initData validation |
| Minigame iframes | JWT + API key | Game-specific JWT + X-Api-Key header |
| Funtico Platform | Bearer token | Static token from LARAVEL_API_TOKEN env var |
| Support/Admin | Basic auth | Username + password from config |
| Marketing API | API key | api_key query parameter |
| Balloon WebSocket | JWT | Token passed as ?token= query param |
Player Authentication Flow
Players authenticate through Telegram's Mini App protocol. The frontend never handles passwords -- identity is guaranteed by Telegram's cryptographic signature.
sequenceDiagram
participant User as Player
participant TG as Telegram
participant FE as Mini App Frontend
participant API as Lucky Funatic API
User->>TG: Opens Lucky Funatic bot
TG->>FE: Launches Mini App with initData
FE->>API: POST /login (initData in body)
API->>API: Validate HMAC-SHA256 signature
API->>API: Extract user info (telegram_id, username, etc.)
API->>API: Create or update user in MySQL
API->>API: Initialize game state if new player
API->>FE: Return JWT token (3-day expiry)
FE->>API: All subsequent requests with Authorization: Bearer <JWT>
How Telegram Validation Works
When Telegram launches a Mini App, it sends initData -- a query string containing user info, auth date, and an HMAC-SHA256 hash. The backend validates this by:
- Computing the secret key:
HMAC-SHA256("WebAppData", telegram_bot_token) - Computing the expected hash from the data fields using that secret key
- Comparing the expected hash with the one Telegram provided
If the hashes match, the user identity is cryptographically verified by Telegram. No password, no OAuth flow, no third-party tokens -- Telegram guarantees the identity.
JWT Token
After successful validation, the API issues a JWT containing:
user_id(internal Lucky Funatic user ID)telegram_idrole- Expiration (3 days from issue)
The JWT is signed with the server's JWT_SECRET and included in all subsequent requests as Authorization: Bearer <token>.
Minigame Authentication
Minigames run in iframes and need a two-layer auth system:
-
API key -- a shared secret (
X-Api-Keyheader) that identifies the minigame service itself. Validated against theMINIGAME_API_KEYconfig value. -
Player JWT -- when a player opens a minigame, the API generates a game-specific JWT via
/toy-box/games/:gameID/iframe. This token is Base62-encoded for URL safety and contains both the user ID and the game ID.
The minigame client uses both to authenticate score submissions and play session starts.
Platform Integration Auth
Communication between Lucky Funatic and the Funtico Platform uses server-to-server bearer tokens:
- Platform -> Lucky Funatic: Requests to
/laravel/*endpoints includeAuthorization: Bearer <LARAVEL_API_TOKEN> - Lucky Funatic -> Platform: Outgoing requests include
Authorization: Bearer <PLATFORM_API_BEARER>
Both tokens are static secrets stored in environment variables.
Middleware Stack
Each route group applies a specific middleware chain. Here's how the main game routes are protected:
flowchart TD
Request["Incoming Request"] --> CORS["CORS Middleware\n(validate origin)"]
CORS --> JWT["JWT Middleware\n(validate token, extract user_id)"]
JWT --> Session["Track Session\n(record activity in Redis)"]
Session --> GameState["Ensure Game State\n(load from Redis, persist if stale)"]
GameState --> Handler["Route Handler"]
The EnsureGameState middleware is worth noting: on every game route request, it pulls the player's game state from Redis and makes it available to the handler. If the state hasn't been persisted to MySQL in over 60 seconds, it triggers a sync. This means the handler always has fresh state available without explicit loading.
Route Group Middleware Summary
| Route Group | Auth | CORS | Extra |
|---|---|---|---|
/ (game) |
JWT | Configurable origins | Session tracking, game state loading |
/login |
None | Permissive (all headers) | -- |
/public |
None | Platform origins only (funtico.com) |
-- |
/minigames |
API key + JWT | Configurable origins | -- |
/laravel |
Bearer token | None | -- |
/balloons |
JWT (query param) | Configurable origins | WebSocket upgrade check |
/support |
Basic auth | Configurable origins | Action logging, feature flag gated |
/ppc |
API key (query param) | Configurable origins | -- |
/metrics |
Basic auth | None | -- |
Security Notes
- All game logic runs server-side. The client sends tap counts and actions, but the server validates everything (energy availability, currency balances, booster eligibility, tournament scores)
- Tapping is capped at 50 taps per request to prevent abuse
- Distributed locks (Redsync) prevent race conditions on currency operations
- Lua scripts in Redis ensure atomic read-modify-write operations
- Balloon game tap positions are validated server-side with a leniency bonus for network latency
- Support endpoints are gated behind both a config flag (
EnableSupportEndpoints) and Basic auth credentials