1. Що таке Auth Server на практиці й чому ми обираємо Keycloak
Почнімо з короткого нагадування. Auth Server (IdP) — це сервіс, який:
- показує користувачеві екрани входу/реєстрації та надання згоди;
- видає OAuth/OIDC‑токени (access_token, id_token, refresh_token);
- публікує discovery‑документ і JWKS‑ключі, щоб ресурсні сервери могли перевіряти ці токени.
У нашому стеку:
- ChatGPT / MCP Jam виступає як OAuth‑клієнт (public client);
- ваш MCP‑сервер — як Resource Server;
- Keycloak — як Auth Server.
Чому Keycloak зручний і для курсу, і для реальних проєктів:
- він open source, тож його легко розгорнути локально або в Docker;
- у нього досить прозора модель сутностей: realm, clients, users, roles;
- практично будь‑яку конфігурацію, яку ви опануєте в Keycloak, потім майже 1:1 перенесете в Auth0/Okta/Cognito. Там ті самі ідеї: client, scopes, redirect URIs, PKCE.
Важлива ідея: ми налаштовуємо не «Keycloak для всього проєкту», а конкретний realm для нашого застосунку ChatGPT. Це своєрідна «пісочниця» для автентифікації саме MCP‑клієнтів.
Коротко кажучи, наприкінці лекції у вас буде:
- власний realm у Keycloak для ChatGPT‑застосунку;
- налаштований public client з Authorization Code + PKCE;
- мінімальний набір scopes і claims;
- розуміння того, як цей токен «живе» у вашому Node‑MCP‑сервері.
2. Базові сутності Keycloak крізь призму MCP
Щоб не губитися в адмінпанелі, розкладемо все по поличках.
Realm: простір налаштувань і користувачів
Realm у Keycloak — це ізольований простір із власними користувачами, клієнтами та політиками. Зручна аналогія — «орендований офіс у бізнес‑центрі»: у кожного своє приміщення, свій список співробітників і свої правила входу.
Для курсу та вашого першого реального застосунку варто створити окремий realm, наприклад giftgenius-mcp або mcp-course. Це дає змогу:
- не чіпати master realm, щоб випадково не зламати адмінку;
- повторно використовувати налаштування й користувачів між різними середовищами (dev / staging / prod) через експорт/імпорт realm.
Client: запис про застосунок (ChatGPT / MCP Jam)
Client у Keycloak — це не «користувач», а застосунок, який звертається до Auth Server по токени. У нашому випадку це не ваш Next.js‑бекенд, а саме MCP‑клієнт: ChatGPT, MCP Jam, а інколи й окремий віджет, якщо ви робите ручний OAuth‑процес у UI.
Ключові поля клієнта:
- client_id — рядковий ідентифікатор;
- тип (public / confidential / bearer-only);
- увімкнені OAuth‑процеси (Standard Flow, Client Credentials тощо);
- список дозволених redirect URI;
- список scopes і protocol mappers (claims у токені).
Для ChatGPT/MCP Jam нам потрібен public client, тому що:
- ChatGPT як клієнт не може безпечно зберігати client_secret;
- MCP Jam як десктопний/браузерний інструмент теж працює в недовіреному середовищі.
User: реальна людина
User — це «живий» користувач: у нього є username, пароль, email, атрибути, групи та ролі. Коли хтось входить через Keycloak, саме його sub та інші дані потрапляють у токен. Далі ви перевіряєте цей токен на MCP‑сервері й мапите його на свої accountId / tenantId.
Для нашої демки цілком достатньо:
- одного‑двох тестових користувачів (наприклад, alice@example.com, bob@example.com);
- за потреби — пари атрибутів на кшталт tenant або plan, якщо хочемо показати, як claims із токена впливають на поведінку tools.
3. Вибір типу клієнта: public, PKCE і чому без секрету
Тепер до суті: як саме налаштувати клієнта в Keycloak під ChatGPT/MCP.
Public vs Confidential: чому не client_secret
У класичному веб‑застосунку ви робите backend, зберігаєте там client_secret, і саме сервер звертається до IdP по токени. Це confidential client: він може зберігати секрет.
У світі ChatGPT усе навпаки:
- OAuth‑клієнтом є сама платформа ChatGPT або утиліта на кшталт MCP Jam;
- ви не контролюєте її код і середовище;
- будь‑який client_secret, який ви передасте ChatGPT, слід одразу вважати скомпрометованим.
Тому ChatGPT/Jam працюють як public clients, тобто без client_secret. Компенсується це через PKCE — Proof Key for Code Exchange.
PKCE простими словами
PKCE — це одноразовий «секрет на сесію». Його мета — не дати перехопити authorization code й обміняти його на токен з іншого місця. Схема така:
- Клієнт генерує випадковий рядок code_verifier.
- Хешує його (зазвичай SHA‑256) й отримує code_challenge.
- Під час редиректу на /authorize надсилає code_challenge і code_challenge_method=S256.
- Після входу користувач повертається з code на redirect URI.
- Клієнт робить POST /token, передаючи code та оригінальний code_verifier.
- Keycloak хешує verifier, порівнює з challenge і, якщо все гаразд, видає токен.
Важливо для нас: усе це за нас робить ChatGPT/MCP Jam. Ми лише маємо в клієнті Keycloak увімкнути підтримку Authorization Code + PKCE (S256) і не вимагати client_secret.
4. Покрокове налаштування Keycloak під MCP‑сценарій
Отже, ми визначилися: для ChatGPT/MCP Jam нам потрібен public client з Authorization Code Flow і PKCE (S256), без client_secret. Тепер подивімося, як ця конфігурація виглядає в налаштуваннях Keycloak.
Припустімо, у вас уже є робочий Keycloak (Docker‑контейнер, локальна інсталяція — неважливо). Нас цікавить логіка налаштувань, а не те, куди саме натискати.
Створюємо новий realm для застосунку
Створімо realm giftgenius-mcp: це окрема область, де будуть:
- користувачі саме для ChatGPT‑застосунку;
- клієнти, через які ChatGPT/MCP Jam проходитиме OAuth;
- власні політики паролів і токенів.
Практична порада: не змішуйте realm, у якому ви авторизуєте співробітників адмінки, із realm для ChatGPT‑клієнтів. Так і безпечніше, і логічно простіше.
Додаємо тестового користувача
Створюємо користувача, скажімо, alice:
- username: alice;
- email: alice@example.com;
- задаємо пароль (для спрощення — без складних політик);
- за бажання додаємо атрибут tenantId=demo-tenant або роль ROLE_PREMIUM.
Пізніше, на MCP‑сервері, ви зможете декодувати токен, витягти sub, email, tenantId і повʼязати їх із власною моделлю користувача.
Створюємо public client для MCP Jam / ChatGPT
Тепер найцікавіше — Client.
На концептуальному рівні параметри мають бути такими:
- Client ID: giftgenius-mcp-client (назва на ваш смак);
- Тип: public / Client Authentication off;
- увімкнено Standard Flow (Authorization Code);
- увімкнено PKCE з методом S256;
- налаштовано redirect URI;
- налаштовано потрібні scopes (openid + ваш кастомний, наприклад mcp:tools).
Вмикаємо Standard Flow і PKCE
На рівні понять:
- вмикаємо Authorization Code Flow (часто це називається «Standard Flow Enabled»);
- у розділі PKCE вказуємо pkceRequired=true і зазвичай явно code_challenge_method=S256.
Чому S256: у сучасній документації OAuth 2.1 і в рекомендаціях OpenAI/Model Context Protocol саме S256 вважається безпечним методом. Plain‑PKCE — небезпечний варіант.
Redirect URI — найкрихкіше місце
Redirect URI мають збігатися буква в букву з тим, що використовуватиме клієнт. Інакше отримаємо помилку invalid_redirect_uri ще на етапі авторизації.
У нашому курсі є два типові клієнти:
- MCP Jam/Inspector для налагодження. Вони зазвичай працюють на http://localhost:PORT/.... Для локального сценарію логічно дозволити redirect такого вигляду:
- http://localhost:5173/* або конкретний шлях, який використовує Jam.
- ChatGPT / Apps SDK у продакшені. Тут redirect URI визначає сама платформа. У реальній інтеграції ви дивитиметеся актуальну документацію OpenAI й прописуватимете потрібний URL, який ChatGPT використовуватиме як callback.
У межах лекції важливе саме розуміння: ChatGPT не може просто взяти будь‑який redirect — він має збігатися з тим, що записано в Auth Server. Тому:
- ніколи не ставте * за принципом «будь‑який URL підійде»;
- для локальної розробки допустимі wildcard-и в межах localhost, але не для продакшену.
Scopes: мінімум, але достатньо
Scopes — це компактний список прав, які запитує клієнт.
Для нашого MCP‑сценарію зазвичай потрібно:
- openid — щоб увімкнути OpenID Connect і отримувати id_token із полем sub, а інколи й email;
- кастомний scope, наприклад mcp:tools, що означатиме «дозволено доступ до MCP‑інструментів».
У Keycloak це можна зробити через Client Scopes:
- залишити openid;
- вимкнути за замовчуванням зайві scopes на кшталт profile і email, якщо вони вам не потрібні;
- додати новий scope mcp:tools, яким ви потім обмежуватимете доступ до tools на Resource Server.
Це важливо з двох причин:
- Без openid ви не отримаєте id_token і частину стандартних OIDC‑полів.
- Без окремого кастомного scope ви не зможете на боці MCP‑сервера чітко сказати: «Ось цей токен можна використовувати для виклику моїх інструментів».
5. Налаштування токенів: час життя, підпис і claims
Час життя access token
У realm‑налаштуваннях Keycloak є розділ Tokens, де ви можете задавати:
- Access Token Lifespan;
- Refresh Token Lifespan та інші тайм‑аути.
Для застосунку ChatGPT важливі короткоживучі access tokens:
- кілька хвилин або годин — цілком нормальне значення;
- якщо токен прострочився, MCP‑сервер відповідає 401, ChatGPT знову запускає OAuth‑процес, а користувач за потреби входить ще раз.
Ідея та сама, що й у документації OpenAI щодо Apps SDK: короткий TTL + оновлення токенів і можливість досить швидко «вивести із сесії» користувача, відкликавши токен на боці IdP.
Refresh tokens для ChatGPT‑клієнта зазвичай або не настільки критичні, або видаються на коротший строк, щоб не тримати «вічні» сесії.
Які claims ми хочемо бачити в токені
Мінімально нам потрібні:
- sub — унікальний ідентифікатор користувача в Keycloak;
- iss — хто видав токен (issuer);
- aud — для якого ресурсу токен (це далі використовується в MCP‑сервері);
- exp — час завершення дії;
- scope — список scopes.
Додатково часто корисні:
- email — якщо ви хочете бачити адресу користувача;
- tenantId або подібний claim — для мультитенантних сценаріїв;
- roles — для додаткової авторизації.
У Keycloak це налаштовується через Protocol Mappers:
- стандартні мапери для email, preferred_username тощо;
- custom mapper-и для атрибутів користувача (user.attribute → claim.name).
Приклад: мапер, який додає email як claim у токен, вказуючи user.attribute=email, claim.name=email.
На боці MCP‑сервера ви зможете взяти ці claims із розпарсеного JWT і:
- повʼязати sub із вашим accountId;
- використовувати tenantId, щоб вибирати лише дані, що належать цьому тенанту;
- використовувати roles для розмежування «тонших» прав.
Підпис токена і JWKS
Keycloak за замовчуванням підписує access/id токени асиметричним алгоритмом (зазвичай RS256) і публікує публічні ключі через JWKS endpoint з OpenID Discovery документа.
Для нас це важливо, тому що MCP‑сервер зможе:
- взяти issuer із токена;
- через /.well-known/openid-configuration знайти JWKS endpoint;
- отримати публічний ключ і перевірити підпис токена локально.
Цю частину ми детальніше розберемо в лекції про MCP‑сервер як захищений ресурс. А вже зараз корисно розуміти, навіщо Keycloak віддає ці метадані.
6. Dynamic Client Registration (DCR): коли це взагалі потрібно
Цей розділ — радше для просунутого рівня. Дотепер ми налаштовували клієнта «вручну» в адмінпанелі, і цього більш ніж достатньо, щоб запустити застосунок. Але протокол OAuth дозволяє клієнтам реєструватися динамічно через окремий endpoint.
У контексті ChatGPT і MCP OpenAI прямо пише, що платформа може використовувати Dynamic Client Registration. Тобто ChatGPT реєструє себе на Auth Server «на льоту» — через registration_endpoint із discovery‑документа.
На рівні Keycloak це виглядає так:
- вмикається DCR на рівні realm;
- ви налаштовуєте політику: хто може реєструвати нових клієнтів і з якими grant types/scopes.
Приклад JSON для реєстрації public client з Authorization Code + PKCE і scope openid mcp:tools може виглядати так:
{
"clientName": "My ChatGPT App",
"redirectUris": ["https://jam.proxy.mcpapps.com/callback"],
"grantTypes": ["authorization_code"],
"responseTypes": ["code"],
"scope": "openid mcp:tools",
"tokenEndpointAuthMethod": "none"
}
Де tokenEndpointAuthMethod: "none" означає якраз public client без client_secret.
Для курсу достатньо просто знати, що:
- DCR корисний, якщо клієнтів багато або вони короткоживучі;
- ChatGPT потенційно може сам зареєструватися у вашому IdP;
- але на перших порах можна обійтися статичним клієнтом, створеним через UI.
7. Як це повʼязано з нашим навчальним застосунком
Згадаємо, що в нас є навчальний MCP‑сервер (наприклад, GiftGenius), який уміє:
- видавати список можливих подарунків;
- зберігати списки бажань користувача;
- згодом — звертатися до commerce‑частини, оформляти замовлення тощо.
Поки MCP‑сервер відкритий, він не знає, хто до нього звертається:
- запит від ChatGPT може логічно бути «від Аліси» або «від Боба», але MCP‑сервер цього не розрізняє;
- ви не можете показувати приватну історію подарунків;
- ви не можете впевнено списати гроші з потрібного акаунта.
Після налаштування Keycloak як Auth Server ситуація змінюється:
- ChatGPT розуміє за .well-known вашого MCP‑ресурсу, що він захищений і вимагає токен.
- ChatGPT відправляє користувача в Keycloak за Authorization Code + PKCE flow.
- Користувач входить (у нашій демці це alice).
- ChatGPT отримує access token, у якому є sub, email, mcp:tools та інші claims.
- ChatGPT викликає інструмент GiftGenius уже з Authorization: Bearer <token>.
- MCP‑сервер, перевіряючи токен, розуміє: «Ага, це Alice з sub=... і tenantId=demo-tenant». І відповідає відповідно.
Цей звʼязок ми завершимо в наступній лекції, де зробимо MCP‑сервер «справжнім» resource server: реалізуємо endpoint метаданих, перевірку токена й привʼязку до користувача.
8. Невеликі практичні приклади (наш стек: TypeScript + Node)
Усе, що нижче, — не «єдино правильний» спосіб, а приклад того, як це може виглядати в типовому стеку Node/TypeScript. Якщо зараз ви більше зосереджені на налаштуваннях у Keycloak, цей розділ можна просто переглянути. А повернутися до нього — коли підʼєднуватимете MCP‑сервер.
Хоча Keycloak здебільшого налаштовують у UI або через Admin REST API, корисно показати кілька фрагментів коду навколо цього. Так буде зрозуміліше, як ви використаєте все це на боці MCP‑сервера.
Припустімо, у нас уже є Node.js‑MCP‑сервер (TypeScript) на базі офіційного SDK.
Конфіг авторизації (issuer і audience)
Створімо невеликий модуль authConfig.ts:
// authConfig.ts
export const authConfig = {
issuer: 'https://auth.my-company.com/realms/giftgenius-mcp',
audience: 'https://mcp.my-company.com', // URL вашого MCP‑сервера
requiredScopes: ['mcp:tools'], // мінімум, який очікуємо в токені
};
Тут issuer — URL realmʼу Keycloak, а audience — ідентифікатор ресурсу. Ми ще використовуватимемо його в налаштуваннях токена й MCP.
Базова верифікація JWT через JWKS
У реальному житті ви, найімовірніше, використаєте бібліотеку на кшталт jsonwebtoken + jwks-rsa або готові утиліти з MCP SDK. Найпростіший каркас може виглядати так:
// verifyToken.ts
import jwt from 'jsonwebtoken';
import jwksClient from 'jwks-rsa';
import { authConfig } from './authConfig';
const client = jwksClient({
jwksUri: `${authConfig.issuer}/protocol/openid-connect/certs`,
});
function getKey(header: any, callback: any) {
client.getSigningKey(header.kid, (err, key) => {
const signingKey = key?.getPublicKey();
callback(err, signingKey);
});
}
export function verifyAccessToken(token: string): Promise<any> {
return new Promise((resolve, reject) => {
jwt.verify(
token,
getKey,
{
audience: authConfig.audience,
issuer: authConfig.issuer,
},
(err, decoded) => (err ? reject(err) : resolve(decoded)),
);
});
}
Звісно, обробка помилок і кешування ключів мають бути акуратнішими. Але загальну ідею ви бачите: Keycloak публікує JWKS‑ключі, ми їх підтягуємо й перевіряємо підпис.
Перевірка scope і витягнення identity
У middleware для MCP‑tools ви зможете зробити щось на кшталт:
// authMiddleware.ts
import { verifyAccessToken } from './verifyToken';
import { authConfig } from './authConfig';
export async function requireAuth(bearerToken: string) {
const token = bearerToken.replace(/^Bearer\s+/i, '');
const decoded: any = await verifyAccessToken(token);
const scopes = (decoded.scope as string).split(' ');
const hasScope = authConfig.requiredScopes.every(s => scopes.includes(s));
if (!hasScope) {
throw new Error('Insufficient scope');
}
return {
userId: decoded.sub,
email: decoded.email,
tenantId: decoded.tenantId,
};
}
А далі вже в обробниках MCP‑інструментів ви використовуватимете userId і tenantId, щоб завантажувати потрібні списки подарунків користувача. Самі інструменти ми вже реалізовували в попередніх модулях. Зараз важливо лише побачити, як токен із Keycloak перетворюється на зрозумілу вашому бекенду identity.
9. Типові помилки під час налаштування Keycloak як MCP Auth Server
Помилка № 1: Використовується confidential client із client_secret.
Інколи за звичкою створюють клієнта типу confidential і намагаються вписати client_secret у конфігурацію MCP/ChatGPT. В екосистемі застосунків ChatGPT це не має працювати й не буде безпечним: ChatGPT — public client, він не може зберігати секрет. Правильний шлях — public client + PKCE.
Помилка № 2: Надто широкі scopes за замовчуванням.
Якщо лишити увімкненими profile, email і ще купу стандартних scopes, а потім видавати такі токени кожному чату — це не найкраща ідея. Краще мінімізувати: openid і конкретний mcp:tools (або кілька прикладних scopes) — цього достатньо для перших версій. Так ви зменшите ризик витоку зайвих даних і зробите поведінку більш передбачуваною.
Помилка № 3: Некоректний redirect URI.
Класика: у Keycloak вказано http://localhost:5173/callback, а MCP Jam звертається до http://localhost:5173/. Або навпаки. У результаті — invalid_redirect_uri і дуже виснажливе налагодження. Завжди перевіряйте точне значення redirect URI в документації Jam/ChatGPT і прописуйте його буква в букву.
Помилка № 4: PKCE не увімкнено або увімкнено не тим методом.
Деякі версії Keycloak вимагають окремо ввімкнути «PKCE required» і вказати метод S256. Якщо цього не зробити, ChatGPT/Jam, який очікує PKCE, може отримати помилку invalid_request зі скаргою на code_challenge. Обовʼязково перевіряйте налаштування PKCE для public‑клієнтів.
Помилка № 5: Неправильні або відсутні claims у токені.
Буває, що в токені немає sub або email, бо відповідний scope або protocol mapper не налаштовано. У результаті на MCP‑сервері ви бачите токен, але не можете мапити його на реального користувача. Рішення: переконайтеся, що потрібні поля (мінімум sub, а краще ще email/tenantId) замаплені в access/id токени.
Помилка № 6: Занадто довгий TTL для access tokens.
З погляду безпеки видавати access tokens на добу або тиждень — погана ідея. У разі витоку токена зловмисник отримає довгостроковий доступ до MCP‑ресурсу. Краще робити access tokens короткоживучими (хвилини або години) й використовувати повторну авторизацію за потреби.
Помилка № 7: Плутанина з realm і використання master.
Інколи перше, що роблять, — створюють клієнта й користувачів прямо в realm master. Потім туди ж «прикручують» ще кілька проєктів — і в підсумку незрозуміло, де що. Краще відразу заводити окремий realm під конкретний застосунок/курс. Це спростить життя і вам, і вашому DevOps.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ