1. Про що ця лекція і чого в ній немає
У цій лекції ми:
- складаємо цілісну картину «трикутника довіри» між MCP Client, MCP Server і MCP Auth Server — разом із користувачем, який стоїть ніби «над» цим трикутником як власник ресурсів;
- обговорюємо потік: хто кому надсилає токен, де користувач входить у систему і чому MCP‑сервер ніколи не бачить його пароля;
- повʼязуємо це з нашим бекендом Next.js/MCP і майбутнім налаштуванням Keycloak/Auth0.
Чого ми не робимо сьогодні:
- не «натискаємо галочки» в Keycloak і не налаштовуємо конкретний IdP;
- не пишемо повноцінну перевірку JWT або інтроспекцію — це теми наступних лекцій (про Auth Server і про MCP Server як захищений ресурс).
Ваше завдання на сьогодні — щоб ви могли взяти аркуш, намалювати стрілки між ChatGPT, вашим сервером і Auth0/Keycloak та без запинки пояснити: де вхід, де токен, де дані.
2. Трикутник довіри: MCP Client, MCP Server, MCP Auth Server
Почнемо з дійових осіб. Технічний «трикутник довіри» утворюють MCP Client, MCP Server і MCP Auth Server. Користувач (User) — окрема роль: власник ресурсів, який стоїть ніби над цим трикутником і надає згоду на доступ. У специфікації MCP і Apps SDK цю архітектуру формалізовано доволі чітко.
User (Resource Owner)
Це людина по той бік екрана. Вона:
- входить у ChatGPT;
- пише запит: «покажи мої замовлення / мої списки подарунків»;
- погоджується привʼязати обліковий запис вашого сервісу до ChatGPT.
Головне: саме вона володіє ресурсами (історією замовлень, профілями, списками подарунків) і саме вона надає згоду на доступ до них.
MCP Client
У нашому контексті це:
- ChatGPT з Apps SDK;
- іноді — MCP Jam Inspector (під час налагодження).
MCP Client уміє:
- читати метадані вашого MCP‑сервера (через .well-known);
- запускати OAuth‑потік у браузері користувача;
- зберігати й додавати токени до викликів MCP‑інструментів.
Важливо памʼятати, що MCP Client — public client, тобто публічний клієнт. Він не зберігає ваш client_secret, тому спілкується з Auth Server-ом як публічний SPA‑застосунок: Authorization Code + PKCE.
MCP Server (Resource Server)
Це ваш бекенд, що реалізує MCP:
- встановлює зʼєднання з ChatGPT;
- оголошує інструменти (tools), ресурси, промпти;
- на кожен виклик інструмента дивиться на заголовок Authorization: Bearer <token>;
- перевіряє токен (підпис, exp, aud, scope) і, якщо все гаразд, виконує бізнес‑логіку.
Принциповий момент: MCP‑сервер не займається входом користувача. Він не бачить паролів, не показує форму входу, не надсилає лист «підтвердьте електронну пошту». Він довіряє лише криптографічно підписаним токенам від Auth Server-а.
MCP Auth Server (Authorization Server / IdP)
Це окремий сервіс автентифікації та авторизації: Keycloak, Auth0, Ory Hydra+Kratos, Okta, Cognito, Azure AD тощо.
Він відповідає за:
- інтерфейс входу (електронна пошта/пароль, SSO, 2FA);
- зберігання облікових записів користувачів;
- видачу токенів (access token, refresh token);
- публікацію OAuth/OIDC‑метаданих (/authorize, /token, jwks_uri, /registration тощо).
Для MCP він має підтримувати OAuth 2.1 для public clients (PKCE S256, dynamic client registration тощо).
Зведена табличка ролей
| Хто | Що робить | Чого не робить |
|---|---|---|
| User | Вводить логін і пароль, надає згоду на доступ до даних | Не спілкується безпосередньо із MCP Server |
| MCP Client (ChatGPT/Jam) | Ініціює OAuth, зберігає токен, викликає MCP tools | Не перевіряє паролі, не перевіряє підпис токена |
| MCP Server | Перевіряє токени, виконує бізнес‑логіку tools | Не показує форму входу, не зберігає паролі |
| MCP Auth Server | Автентифікує користувача, видає токени | Не знає про ваші MCP‑інструменти та їхню бізнес‑логіку |
Якщо у вас у голові все це змішувалося в один «великий сервер, який робить усе», — час розділити ролі.
3. Як виглядає flow: від «немає токена» до захищеного виклику інструментів
Тепер подивімося на потік повідомлень. У специфікації MCP цей процес називають «The Flow»: discovery → redirect → code → token → authorized calls.
Крок 0. Спроба викликати захищений інструмент без токена
Користувач пише: «Покажи мої збережені ідеї для подарунків».
ChatGPT як MCP Client вирішує: «для цього потрібно викликати інструмент getUserGiftLists на нашому MCP‑сервері». Він робить виклик без токена (бо користувач ще не входив у систему).
Ваш MCP‑сервер:
- бачить відсутній або некоректний Authorization‑заголовок;
- відповідає 401 Unauthorized і додає заголовок WWW-Authenticate: Bearer resource_metadata="https://api.giftgenius.com/.well-known/oauth-protected-resource" з посиланням на метадані захищеного ресурсу (resource metadata; про це — нижче).
Приблизно так (лише логіка, не повний HTTP):
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer resource_metadata="https://api.giftgenius.com/.well-known/oauth-protected-resource"
ChatGPT бачить цей заголовок і розуміє: «ага, ресурс захищено OAuth. Потрібно виконати OAuth‑потік і привʼязати обліковий запис».
Discovery: .well-known/oauth-protected-resource
Далі MCP Client запитує у вашого сервера метадані:
GET /.well-known/oauth-protected-resource
Сервер відповідає JSON‑документом з ідентифікатором ресурсу та списком авторизаційних серверів, у яких потрібно отримувати токени.
Мінімальний приклад (детально налаштуємо пізніше; зараз важлива сама ідея):
{
"resource": "https://api.giftgenius.com",
"authorization_servers": [
"https://auth.giftgenius.com"
],
"scopes_supported": ["gifts.read", "gifts.write"]
}
Тут:
- resource — канонічний ID вашого ресурсу; його потім мають використовувати як audience або resource під час видачі токена;
- authorization_servers — список Auth Server-ів, у яких ChatGPT може запитати токен;
- scopes_supported — які «права доступу» ваш MCP‑сервер узагалі розуміє.
Authorization Request: переспрямування на Auth Server
Отримавши метадані, MCP Client переходить на Auth Server. Він відкриває в браузері сторінку:
GET https://auth.giftgenius.com/authorize
?response_type=code
&client_id=chatgpt-giftgenius
&redirect_uri=... (зворотний URL MCP Clientʼа)
&code_challenge=...
&code_challenge_method=S256
&scope=openid gifts.read
&resource=https://api.giftgenius.com
Користувач:
- бачить знайомий екран входу (наприклад, Keycloak або Auth0);
- вводить логін і пароль, проходить 2FA;
- підтверджує, що ChatGPT може читати його подарункові списки (scope gifts.read).
Code → Token: обмін коду на токен з PKCE
Після успішного входу Auth Server переспрямовує користувача назад до MCP Clientʼа з code. MCP Client:
- робить POST на /token;
- передає code і code_verifier (який відповідає code_challenge з кроку вище).
Auth Server перевіряє PKCE: хешує code_verifier і порівнює його з початковим code_challenge. Якщо все гаразд і клієнт справді той самий, хто починав потік, тоді він:
- видає короткочасний access_token (зазвичай JWT);
- у ньому вказує:
- sub — ID користувача в Auth Server-і;
- aud або resource — ваш MCP‑сервер;
- scope — дозволені дії (gifts.read, openid тощо).
Authenticated Request: виклик MCP‑інструмента з токеном
Тепер MCP Client готовий знову викликати ваш інструмент — уже із заголовком:
Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...
MCP‑сервер:
- перевіряє підпис токена (за JWK Auth Server-а) або через introspection;
- перевіряє строк чинності (exp);
- перевіряє aud / resource — що токен справді видано для https://api.giftgenius.com;
- дивиться на scope і вирішує, чи можна викликати getUserGiftLists.
Після цього він уже спокійно звертається до вашої БД за конкретним userId і повертає персональні списки подарунків.
Зверніть увагу: до цього моменту ми говорили лише про мережевий потік — як токен отримується і доходить до MCP‑сервера. Далі важливо зрозуміти, як із sub та інших claims у токені виходить конкретний userId у вашій БД. Саме тут у дію вступає identity bridge.
4. Identity Bridge: як user ChatGPT перетворюється на userId у вашій БД
Найцікавіший фрагмент архітектури — це «міст ідентичності» (identity bridge). У специфікації MCP прямо підкреслюється: MCP‑сервер не знає про користувачів ChatGPT, він спирається на дані в токені від Auth Server-а.
Схема приблизно така:
flowchart TD User[User у ChatGPT] -->|Login/SSO| Auth[Auth Server] Auth -->|JWT: sub, email, tenant| MCP[MCP Server] MCP -->|userId/tenantId| DB[(Ваша БД)]
Покроково це виглядає так.
По‑перше, Auth Server на своєму боці знає своїх користувачів: у нього є сутності user, email, id, можливо, tenant, roles. Після успішного входу він додає цю інформацію в токен (у claims):
{
"sub": "auth0|abc123",
"email": "user@example.com",
"given_name": "Alice",
"https://giftgenius.com/tenant": "tenant-42",
"scope": "openid gifts.read",
"aud": "https://api.giftgenius.com"
}
По‑друге, MCP Server під час перевірки токена дістає ці claims і визначає, хто це «в його світі». Наприклад:
- якщо sub уже є в таблиці User.authProviderId — беремо повʼязаний userId;
- якщо ні — створюємо локальний запис (динамічне створення, on‑the‑fly provisioning) і повʼязуємо.
Типовий фрагмент TypeScript‑коду на стороні MCP‑сервера (спрощений, без перевірки підпису) може виглядати так:
type TokenClaims = {
sub: string;
email?: string;
scope?: string;
};
async function mapClaimsToUserId(claims: TokenClaims): Promise<string> {
const user = await db.user.findUnique({ where: { authSub: claims.sub } });
if (user) return user.id;
const created = await db.user.create({
data: { authSub: claims.sub, email: claims.email ?? null }
});
return created.id;
}
По‑третє, уже знаючи userId, MCP‑сервер дістає все, що потрібно: списки подарунків, історію замовлень, налаштування, тариф.
Отже, Auth Server стає «мостом» між зовнішнім світом (ChatGPT, Google, SSO) і вашим внутрішнім світом (customer_id у БД замовлень).
5. Чому потрібно розділяти Auth Server і MCP Server
Може виникнути спокуса: «А давайте мій MCP‑сервер сам і екран входу покаже, і токен видасть». Формально це можливо (ви можете вбудувати в нього міні‑IdP), але з погляду архітектури це погана ідея. Причини тут досить практичні.
По‑перше, безпека й масштабованість. Auth Server — це «важкий механізм»: 2FA, social login, політики паролів, блокування облікового запису, відновлення доступу, аудит входів, можливо, сертифікації. Писати це заново в кожному мікросервісі (і в кожному MCP‑сервері) — прямий шлях до хаосу та проблем із відповідністю вимогам на кшталт PCI‑DSS. Значно простіше делегувати це Keycloak/Auth0 і просто перевіряти їхній токен.
По‑друге, різноманіття клієнтів. Сьогодні у вас лише ChatGPT. Завтра ви підʼєднаєте Claude Desktop, свій вебфронтенд на Next.js або мобільний застосунок. Усі вони можуть використовувати один і той самий Auth Server і ту саму схему OAuth 2.1, а ваш MCP‑сервер просто продовжить перевіряти токени. Вам не доведеться переписувати бізнес‑логіку під кожного нового клієнта.
По‑третє, чистота коду. MCP Server в ідеалі:
- уміє публікувати /.well-known/oauth-protected-resource;
- уміє перевіряти Bearer-токен і діставати з нього userId, scopes, tenant;
- реалізує бізнес‑інструменти (orders, gifts, profiles).
Уся UI‑логіка входу — форми, верстка, соціальні логіни — живе в Auth Server-і й не захаращує бекенд.
6. Як це виглядає в нашому навчальному застосунку GiftGenius
Повернімося до застосунку, який ми розробляємо протягом курсу. Припустімо, у нас є:
- ChatGPT App «GiftGenius» із віджетом (Apps SDK), який уміє підбирати подарунки;
- MCP‑сервер на Node/Next.js, який надає інструменти:
- searchGifts — анонімний, не потребує входу;
- getSavedGiftLists — персональний, потребує автентифікації;
- Auth Server (пізніше — Keycloak/Auth0), де кожен користувач має обліковий запис.
Сценарій для анонімного та автентифікованого користувача
Якщо користувач просто пише «підбери подарунок для брата, 30 років, любить настолки», наш застосунок може:
- викликати анонімний інструмент searchGifts;
- показати в інтерфейсі рекомендації.
У цьому разі:
- токен не потрібен;
- MCP‑сервер просто виконує запит (наприклад, до вашого каталогу або стороннього API).
Щойно користувач каже «збережи це в мої списки» або «покажи мої збережені ідеї», модель вирішує викликати захищений інструмент getSavedGiftLists. Сервер відповідає 401 + WWW-Authenticate з resource_metadata. ChatGPT запускає OAuth‑майстер «Link GiftGenius account», проводить користувача через вхід і отримує токен.
Далі, під час кожного захищеного виклику:
- MCP Server уже бачить Authorization: Bearer ...;
- дістає userId із токена;
- фільтрує дані за цим userId.
Саме завдяки цьому ми можемо:
- розділяти дані різних користувачів;
- безпечніше показувати історію замовлень і список обраного;
- будувати commerce‑функції (пізніше в курсі).
Архітектура бекенду: middleware + обробники інструментів
На практиці в коді Node/Next.js це часто виглядає як ланцюжок: «middleware автентифікації → бізнес‑обробник інструмента». У лекції про реалізацію tool‑обробників ми вже підкреслювали, що в них потрібно передавати контекст: user_id, токени, налаштування.
Фрагмент коду може бути таким:
// auth-context.ts
export type AuthContext = {
userId: string | null; // null для анонімних викликів
scopes: string[];
};
Middleware, який вішається на всі MCP‑ендпойнти:
// mcp-auth-middleware.ts
export async function buildAuthContext(req: Request): Promise<AuthContext> {
const header = req.headers.authorization || "";
const token = header.replace(/^Bearer\s+/i, "");
if (!token) return { userId: null, scopes: [] }; // анонімний користувач
const claims = await verifyAndDecodeToken(token); // перевірка та декодування токена
const userId = await mapClaimsToUserId(claims);
const scopes = (claims.scope || "").split(" ");
return { userId, scopes };
}
А сам обробник інструмента отримує цей контекст:
// tools/getSavedGiftLists.ts
export async function getSavedGiftLists(_args: {}, ctx: AuthContext) {
if (!ctx.userId) throw new Error("User must be authenticated");
return db.giftList.findMany({
where: { ownerId: ctx.userId }
});
}
Суть у тому, що tool‑обробник не знає нічого ні про OAuth, ні про PKCE. Він просто працює з «очевидним» userId. Уся OAuth‑магія від нього схована — у MCP‑клієнті та в Auth‑middleware.
7. Візуальні схеми: як разом працюють Client, Server і Auth
Ми вже покроково розібрали flow у розділі 3 — текстом. Іноді простіше один раз намалювати, ніж сім разів пояснювати, тож зараз покажемо ті самі взаємодії у вигляді двох діаграм.
Скелет взаємодії (The Triangle of Trust)
flowchart TD U[User] -->|1. Login / Consent| A[MCP Auth Server] U -->|2. Спілкується| C["MCP Client (ChatGPT)"] C -->|3. OAuth Flow| A C -->|4. Bearer Token| S[MCP Server] S -->|5. Data| C
Схема читається так.
Спочатку користувач входить через Auth Server, який, по суті, підтверджує його особу й видає токен. MCP Client керує цим процесом, а потім використовує токен, щоб звертатися до MCP‑сервера. MCP‑сервер не бачить логіна й пароля: він бачить лише токен і вирішує, що дозволено.
Потік від запиту до відповіді
sequenceDiagram participant User participant ChatGPT as MCP Client participant Auth as Auth Server participant MCP as MCP Server User->>ChatGPT: "Покажи мої списки подарунків" ChatGPT->>MCP: callTool(getSavedGiftLists) (без токена) MCP-->>ChatGPT: 401 + WWW-Authenticate (resource_metadata) ChatGPT->>Auth: /authorize + PKCE User->>Auth: Вводить логін/пароль, дає consent Auth-->>ChatGPT: redirect + code ChatGPT->>Auth: /token + code_verifier Auth-->>ChatGPT: access_token (JWT) ChatGPT->>MCP: callTool(getSavedGiftLists) + Authorization: Bearer ... MCP-->>ChatGPT: JSON з особистими списками ChatGPT-->>User: Відображений список у віджеті
Ця діаграма — те, що ви маєте вміти «розповісти із заплющеними очима» наприкінці модуля.
8. Трішки глибше: кілька ресурсів, кілька клієнтів, DCR
Перевага такої архітектури в тому, що вона добре масштабується.
По‑перше, у вас може бути кілька MCP‑серверів (наприклад, один — про подарунки, інший — про замовлення) і один Auth Server, який видає токени з різними aud/resource. Кожен ресурсний сервер зобовʼязаний перевіряти, що токен справді призначено саме для нього. Інакше виникне класична проблема «confused deputy», коли токен для одного сервісу приймається іншим.
По‑друге, у вас може бути багато клієнтів:
- ChatGPT App;
- ваш власний фронтенд;
- мобільний застосунок;
- інтеграція партнера через MCP Gateway.
Усі вони будуть:
- читати /.well-known/oauth-protected-resource;
- дізнаватися, де розташований Auth Server;
- проходити OAuth 2.1 flow;
- отримувати токени й викликати MCP‑сервер.
По‑третє, сучасні Auth Server-и дедалі частіше підтримують Dynamic Client Registration (DCR) — можливість динамічно реєструвати клієнтів через API. MCP‑специфікація якраз і передбачає таку можливість: клієнт (ChatGPT/Jam) може автоматично реєструвати себе на Auth Server-і за його registration_endpoint.
У цьому модулі нам важливо розуміти, що:
- MCP Client, MCP Server і Auth Server спілкуються через стандартизовані discovery‑документи й токени;
- вам не потрібно «жорстко задавати» всіх клієнтів у коді бекенду;
- ви можете розбудовувати екосистему, не ламаючи наявну модель авторизації.
9. Типові помилки в розумінні архітектури авторизації MCP
Помилка № 1: «MCP‑сервер має сам автентифікувати користувача».
Іноді розробники намагаються вбудувати форму входу прямо в MCP‑сервер, а потім передавати логін і пароль через інструменти. Це ламає саму ідею OAuth. MCP‑сервер не має бачити пароль за жодних обставин. Вхід і згода — зона відповідальності Auth Server-а. MCP‑сервер працює лише з токенами та їхніми claims.
Помилка № 2: Плутанина між MCP Client і MCP Server.
Буває, що ChatGPT сприймають як «частину мого бекенду» і намагаються, наприклад, зберігати в ньому якісь секрети або очікувати, що він сам перевірить права доступу. Насправді MCP Client лише ініціює OAuth і додає токени. Перевірка токена і прав — завдання MCP‑сервера, а не ChatGPT.
Помилка № 3: «API‑ключ у .env замість OAuth».
Класичний антипатерн: зробити один великий SERVICE_API_KEY, покласти його в .env MCP‑сервера й вважати, що проблему вирішено. У такому варіанті немає розмежування прав між користувачами, не можна безпечно показувати персональні дані або виконувати покупки. Усе робиться «від імені сервісу», а не від імені користувача. Це повністю суперечить цілям авторизації в ChatGPT Apps.
Помилка № 4: Ігнорування audience і resource.
Якщо MCP‑сервер приймає будь‑який валідний JWT із коректним підписом і не дивиться на aud/resource, тоді будь‑який токен, виданий для іншого сервісу тим самим Auth Server-ом, може бути використаний для виклику ваших інструментів. Це пряме порушення моделі безпеки OAuth. Сервер зобовʼязаний перевіряти, що токен видано саме для його resource.
Помилка № 5: Змішування auth‑логіки та бізнес‑логіки.
Іноді в tool‑обробники починають тягнути весь розбір токена, перевірку підпису, роботу з JWK тощо. У підсумку код стає крихким і важким у супроводі. Значно правильніше відокремити шар «перевірка токена, мапінг на userId» (middleware) від шару «власне логіка інструмента», який отримує вже зрозумілий AuthContext.
Помилка № 6: Очікування, що ChatGPT «сам усе зробить» без .well-known.
Без правильного ендпойнта /.well-known/oauth-protected-resource MCP‑клієнт просто не знає, де розташований ваш Auth Server і які scopes потрібні. Результат — чат мовчки «не вміє входити», а розробник довго дивиться в порожні журнали. Правильний шлях такий: MCP‑сервер чітко оголошує свої вимоги до авторизації через .well-known, клієнт читає їх і будує потік.
Помилка № 7: Забутий користувач у бізнес‑логіці.
Іноді, навіть правильно налаштувавши OAuth і мапінг токена на userId, розробники не використовують це в запитах до БД. Наприклад, забувають фільтрувати за ownerId = userId. Тоді будь‑який автентифікований користувач може побачити чужі дані. Наявність токена — це лише перший крок; другим кроком завжди є коректне використання userId і scope у бізнес‑коді.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ