Подключить Mars ID
к своему проекту
Вход через Telegram вместо своей формы регистрации. Работает и на нашем хостинге, и на твоём — Vercel, Render, VPS, что угодно. Бесплатно, ключи выдаём за час.
Выбери свой случай
Способ зависит от того, где живёт твой проект. Дальше — только твоя ветка, остальное можно не читать.
Замок на готовый сайт
Статика на *.marshub.uz или страница на pages.marshub.uz. Пускаем только своих — без бэкенда.
Свой бэкенд на нашем домене
Node, Python, PHP, Go на твой.marshub.uz. Cookie уже приходит — остаётся её проверить.
Свой хостинг, свой домен
Vercel, Render, Railway, VPS, локалхост. Стандартный OAuth2 / OpenID Connect — понимает любой фреймворк.
Telegram Mini App
Приложение внутри Telegram: юзер уже опознан, логин не показываем вообще.
Замок на готовый сайт
Сайт уже задеплоен, кода трогать не хочется, но видеть его должны не все. Проверку делает наш прокси до того, как запрос дойдёт до твоих файлов.
Целый сайт имя.marshub.uz
Напиши нам имя поддомена и список Telegram ID, кого пускать. Мы включаем замок в базе — рестарт и правка кода не нужны.
- → пустой список = пустим любого, кто вошёл в Mars ID
- → список ID = пустим только их, остальным 403
- → без замка сайт публичный, как и был
Гостя без входа само перекинет на id.marshub.uz/login и вернёт обратно на твою страницу.
Одна страница pages.marshub.uz
Тут всё сам, без нас. У каждой опубликованной страницы есть настройка доступа:
- → public — открыта всем
- → password — общий пароль, аккаунт не нужен
- → marsid — вход через Telegram, можно со списком ID
Через MCP-инструмент marshub_set_page_access это делает и твой AI-агент.
Что это не умеет: замок отвечает на вопрос «пускать или нет». Если внутри нужны роли, личный кабинет или «этот заказ мой» — нужен бэкенд, это путь 2.
Свой хостинг, любой домен
Cookie живёт только на .marshub.uz — снаружи её нет. Поэтому для проекта на Vercel, Render,
своём VPS или на localhost работает стандартный OpenID Connect. Mars ID — полноценный OIDC-провайдер,
его понимает почти любая библиотека авторизации из коробки.
Шаг 1 — получить ключи
Напиши @marvinaka и пришли три вещи:
Название проекта
Как подписать в списке клиентов
Callback URL
Точный адрес, куда вернуть юзера. Можно несколько: прод, стейдж, localhost
Кому доступ
Всем с Mars ID / только ученикам / только сотрудникам
В ответ придут client_id и client_secret.
Callback сверяется побуквенно — никаких масок и «начинается с»: лишний слэш в конце уже другой адрес.
Шаг 2 — настройки для библиотеки
Если твой фреймворк умеет OIDC (NextAuth/Auth.js, Passport, Authlib, Spring Security, Keycloak-adapter, Grafana, Forgejo, GlitchTip…) — просто вставь эти значения, кода почти не будет.
| Параметр | Значение |
|---|---|
| Issuer | https://id.marshub.uz |
| Discovery / well-known | https://id.marshub.uz/.well-known/openid-configuration |
| Authorization URL | https://id.marshub.uz/oauth/authorize |
| Token URL | https://id.marshub.uz/oauth/token |
| UserInfo URL | https://id.marshub.uz/oauth/userinfo |
| Scopes | openid profile email |
| Response type | code |
| id_token алгоритм | HS256 (подпись — твоим client_secret) |
| Аутентификация клиента | client_secret_post или client_secret_basic |
Чего у нас нет: PKCE, refresh-токенов и RS256/JWKS. Токены живут час, код обменивается один раз за 5 минут.
Для SPA без бэкенда это значит: обменивать код должен сервер, класть client_secret в браузер нельзя.
Шаг 3 — если пишешь руками
// 1. Отправляем юзера на Mars ID app.get('/login', (req, res) => { const state = crypto.randomBytes(16).toString('hex'); req.session.state = state; // проверим на возврате const u = new URL('https://id.marshub.uz/oauth/authorize'); u.searchParams.set('client_id', process.env.MARS_CLIENT_ID); u.searchParams.set('redirect_uri', 'https://myapp.uz/callback'); u.searchParams.set('response_type', 'code'); u.searchParams.set('scope', 'openid profile email'); u.searchParams.set('state', state); res.redirect(u.toString()); }); // 2. Юзер вернулся с кодом — меняем код на токены app.get('/callback', async (req, res) => { if (req.query.state !== req.session.state) return res.status(400).send('bad state'); const tokens = await fetch('https://id.marshub.uz/oauth/token', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ grant_type: 'authorization_code', code: req.query.code, redirect_uri: 'https://myapp.uz/callback', // тот же, что на шаге 1 client_id: process.env.MARS_CLIENT_ID, client_secret: process.env.MARS_CLIENT_SECRET, }), }).then(r => r.json()); // 3. Кто это — из id_token (подписан твоим client_secret) или запросом: const user = await fetch('https://id.marshub.uz/oauth/userinfo', { headers: { Authorization: `Bearer ${tokens.access_token}` }, }).then(r => r.json()); // user = { sub, name, preferred_username, handle, email, // role, tg, is_staff, is_student, core_id } req.session.user = user; res.redirect('/'); });
Telegram Mini App
Внутри Telegram человека уже опознали — показывать кнопку входа глупо. Telegram отдаёт подписанную строку
initData, ты пересылаешь её нам, мы проверяем подпись и ставим ту же cookie __mars_id.
// в Mini App, сразу на старте const tg = window.Telegram.WebApp; const me = await fetch('https://id.marshub.uz/api/auth/telegram-webapp', { method: 'POST', headers: { 'Content-Type': 'application/json' }, credentials: 'include', // иначе cookie не сохранится body: JSON.stringify({ initData: tg.initData }), }).then(r => r.json()); // me = { id, name, handle, role, tg, needs_handle } // needs_handle: true → человек ещё не выбрал себе имя
Запрос идёт с чужого домена, поэтому origin твоего Mini App нужно внести в белый список —
напиши нам адрес. Подпись initData действительна сутки.
Что где лежит
Адреса
| Endpoint | Метод | Что делает |
|---|---|---|
| /login?next=URL | GET | Страница входа, после успеха вернёт на URL |
| /logout | GET | Удалить cookie |
| /api/verify | GET | Проверить токен из Authorization: Bearer или cookie → { id, name, role, tg } |
| /api/me | GET | То же, но только по cookie. Из браузера с другого домена — нужен белый список origin'ов |
| /api/auth/telegram-webapp | POST | initData Mini App → cookie |
| /.well-known/openid-configuration | GET | OIDC Discovery |
| /oauth/authorize | GET | Начало OAuth-флоу |
| /oauth/token | POST | code → access_token + id_token |
| /oauth/userinfo | GET | Данные по Bearer access_token |
| /health | GET | Живой ли сервис |
Всё относительно https://id.marshub.uz
Что внутри токена
| Поле | Что это |
|---|---|
| sub | UUID пользователя, не меняется |
| name | Имя из Telegram |
| handle | Ник (aziz), может быть null до онбординга |
| role | member · student · intern · tutor · mentor · team · admin |
| tg | Telegram ID, число |
| handle@marshub.uz | |
| is_staff | Сотрудник школы (из HR-базы) |
| is_student | Действующий ученик (из Core) |
| core_id | ID в основной базе школы, если связан |
| exp | Живёт 30 дней |
role — это «кто человек», а не «что ему можно».
Права держи у себя: is_staff/is_student для грубого разделения, свой список — для точного.
Грабли, на которые все наступают
- 01Cookie httpOnly. Из JavaScript её не прочитать — и не надо. Проверка только на сервере.
- 02На localhost cookie не придёт. Она привязана к .marshub.uz. Для локальной разработки бери OIDC с callback на localhost — мы такой зарегистрируем.
- 03Проверяй алгоритм. Если верифицируешь подпись сам — всегда явно указывай
HS256, иначе библиотека может принять токен без подписи. - 04Не верь фронтенду. Роль, id и «я админ» приходят из токена на сервере. Всё, что прислал браузер в теле запроса, — не доказательство.
- 05Права меняются не мгновенно. Токен живёт 30 дней: сняли роль — в старом токене она ещё есть. Если критично, дёргай
/api/verifyили храни отзыв у себя. - 06Кодируй next. Адрес возврата — через
encodeURIComponent, иначе потеряются параметры после ?.
Отдай задачу агенту
Claude Code, Cursor, Mars Code — любому агенту хватит одной страницы фактов. Скинь ему ссылку и задачу, дальше он сам.
marshub.uz/id/llms.txt
Вся эта страница в машинном виде: эндпоинты, поля токена, флоу, ограничения.
marshub.uz/llms.txt
То же про хостинг: как агенту задеплоить сайт и поставить замок.
Промпт, который можно скопировать как есть:
Подключи вход через Mars ID к моему проекту. Факты и эндпоинты возьми здесь: https://marshub.uz/id/llms.txt Мой проект: <стек, фреймворк> Живёт на: <имя.marshub.uz | внешний хостинг | Telegram Mini App> Требования: - если проект на *.marshub.uz — читать cookie __mars_id и проверять её запросом на https://id.marshub.uz/api/verify (Authorization: Bearer), общий AUTH_SECRET не использовать; - если проект на внешнем домене — OAuth2 authorization code (issuer https://id.marshub.uz), client_id/secret из переменных окружения, без PKCE и refresh-токенов — их у провайдера нет; - неавторизованного редиректить на https://id.marshub.uz/login?next=<url-encoded адрес возврата>; - роль и права проверять только на сервере, фронтенду не верить; - добавить ссылку выхода на https://id.marshub.uz/logout.
Нужны ключи или маршрут?
Регистрация OIDC-клиента, замок на сайт, свой поддомен, белый список для Mini App — всё это делаем руками и быстро. Напиши, что подключаешь.