Інтеграція входу SIWX із зовнішніми сервісами через JWT та JWKS
Сесія SIWX за замовчуванням зберігається в захищеній HttpOnly куці: доступ до неї має виключно ваш власний сервер. Проте багато зовнішніх платформ (наприклад, провайдери вбудованих гаманців із кастомною автентифікацією, такі як Coinbase CDP , зовнішні IdP або ваші власні мікросервіси) приймають авторизацію лише у вигляді JWT-токенів, підписаних вашими публічними ключами.
Цей посібник зв'язує їх в єдину безшовну систему. За підсумком ви отримаєте:
GET /api/siwx/token— видає короткоживучий JWT для гаманця, що увійшов через SIWX.GET /api/siwx/jwks— публікує криптографічні публічні ключі для перевірки підпису.- Підключення Coinbase CDP Custom Auth, який створює вбудований смарт-гаманець для авторизованого користувача.
- Власний мікросервіс, що надійно довіряє токену без прямого доступу до бази сесій.
Архітектура та життєвий цикл
Wallet ──signs CAIP-122──▶ POST /api/siwx/verify ──▶ session cookieBrowser ──cookie──▶ GET /api/siwx/token ──▶ JWT (ES256, 10 minutes)Provider ──▶ GET /api/siwx/jwks ──▶ public keys ──▶ checks the JWT signature, iss, aud, expProvider ──▶ finds or creates the user of `sub`Перед початком
- Додаток з посібника Full-Stack React, з маршрутами SIWX та файлом
src/lib/authStores.ts. - Публічний HTTPS URL для додатку: зовнішній провайдер завантажує ваш JWKS за цією адресою.
Крок 1: Генерація приватного ключа підпису
Виконайте цей скрипт один раз і збережіть отриманий приватний JWK у секретах середовища вашого проекту. За замовчуванням використовується сучасний алгоритм ES256 (крива P-256):
import { generateSiwxJwtKey } from '@tuwaio/sdk/siwx/server'; const { privateJwk, kid } = await generateSiwxJwtKey('ES256');console.log(`kid: ${kid}`);console.log(`SIWX_JWT_PRIVATE_KEY='${JSON.stringify(privateJwk)}'`);node scripts/generate-siwx-jwt-key.mjsЗбережіть виведений рядок у змінній оточення SIWX_JWT_PRIVATE_KEY у файлі .env.local та в панелі вашого хостингу.
Крок 2: Увімкнення JWT у маршрутизаторі API
Опція jwt автоматично додає обробку маршрутів /token та /jwks до вашого обробника сесій SIWX:
import type { EvmVerifyClient } from '@tuwaio/evm-sdk/siwx';import { importSiwxJwtKey } from '@tuwaio/sdk/siwx/server';import { createSiwxApiHandler } from '@tuwaio/sdk/siwx/server-next';import { createPublicClient, http } from 'viem'; import { appChains } from '@/configs/appConfig';import { nonceStore, sessionStore } from '@/lib/authStores'; const appUrl = new URL(process.env.NEXT_PUBLIC_APP_URL ?? 'http://localhost:3000'); const clients = new Map<number, EvmVerifyClient>( appChains.map((chain) => [chain.id, createPublicClient({ chain, transport: http() })]),); export const { GET, POST, DELETE } = createSiwxApiHandler({ sessionStore, nonceStore, policy: { expectedDomain: appUrl.host, expectedUri: appUrl.origin, requireExpirationTime: true, maxIssuedAtAgeSeconds: 300, }, verifyOptions: { publicClient: (chainId) => clients.get(chainId) }, jwt: { signingKey: importSiwxJwtKey({ privateKey: process.env.SIWX_JWT_PRIVATE_KEY ?? '' }), issuer: appUrl.origin, audience: 'my-app', },});GET /api/siwx/tokenповертає{ token, expiresAt }для поточної сесії із заголовкомCache-Control: no-store(або 401, якщо сесія відсутня).GET /api/siwx/jwksвіддає набір публічних ключів із кешуванням на 5 хвилин.
Крок 3: Підключення Coinbase CDP Custom Authentication
У панелі керування CDP Portal перейдіть у налаштування кастомної автентифікації вашого проекту та вкажіть параметри:
| Параметр CDP | Значення |
|---|---|
| JWKS endpoint URL | https://<ваш-домен>/api/siwx/jwks |
| Issuer (iss) | URL вашого додатку, наприклад https://<ваш-домен> |
| Audience (aud) | Значення audience з налаштувань (my-app) |
| User identifier claim | Залиште порожнім: за замовчуванням CDP використовує стандартне поле sub |
Створіть клієнтський хелпер для отримання актуального токена:
export async function getSiwxJwt(): Promise<string | undefined> { const res = await fetch('/api/siwx/token', { cache: 'no-store' }); if (!res.ok) return undefined; const { token } = (await res.json()) as { token: string; expiresAt: number }; return token;}Огорніть додаток у провайдер Coinbase CDP:
'use client'; import { CDPHooksProvider, useAuthenticateWithJWT, useIsSignedIn } from '@coinbase/cdp-hooks';import { useSiwxSession } from '@tuwaio/sdk/siwx';import { type ReactNode, useEffect } from 'react'; import { getSiwxJwt } from '@/lib/siwxJwt'; function CdpSignIn() { const { isAuthenticated } = useSiwxSession(); const { isSignedIn } = useIsSignedIn(); const { authenticateWithJWT } = useAuthenticateWithJWT(); useEffect(() => { if (isAuthenticated && !isSignedIn) void authenticateWithJWT(); }, [isAuthenticated, isSignedIn, authenticateWithJWT]); return null;} export function CdpProvider({ children }: { children: ReactNode }) { return ( <CDPHooksProvider config={{ projectId: process.env.NEXT_PUBLIC_CDP_PROJECT_ID ?? '', customAuth: { getJwt: getSiwxJwt }, ethereum: { createOnLogin: 'eoa' }, }} > <CdpSignIn /> {children} </CDPHooksProvider> );}Крок 4: Перевірка токена у власних бекенд-сервісах
Будь-який мікросервіс може автономно верифікувати токен за допомогою функції verifySiwxJwt:
import { type SiwxJwks, verifySiwxJwt } from '@tuwaio/sdk/siwx/server'; const APP_URL = 'https://app.example.com'; export async function walletOf(request: Request): Promise<string | null> { const token = request.headers.get('authorization')?.replace(/^Bearer /, ''); if (!token) return null; const jwks = (await (await fetch(`${APP_URL}/api/siwx/jwks`)).json()) as SiwxJwks; const claims = await verifySiwxJwt(token, { jwks, issuer: APP_URL, audience: 'my-app' }); return claims?.sub ?? null;}Клейми всередині виданого токена
| Клейм | Призначення та зміст |
|---|---|
iss | Видавець токена (URL вашого додатку) |
aud | Цільова аудиторія, якщо була налаштована |
sub | Ідентифікатор користувача: прив'язаний ID або адреса гаманця (у нижньому регістрі для EVM, Base58 для Solana) |
caip10 | Повний ідентифікатор гаманця в стандарті CAIP-10 (наприклад, eip155:8453:0xAb58...) |
chain_id | CAIP-2 ідентифікатор мережі під час входу |
Безпека та ротація ключів
- Короткий час життя токенів: За замовчуванням 10 хвилин достатньо. Ніколи не зберігайте токени в
localStorage. - Зберігайте ключ на сервері:
SIWX_JWT_PRIVATE_KEY— це строгий секрет розгортання. - Ротація без простоїв: Згенеруйте новий ключ, призначте його як
signingKey, а старий публічний перемістіть уjwt.previousKeys.
Локальна розробка
Зовнішній провайдер завантажує ваш JWKS через інтернет, тому http://localhost:3000 для нього недоступний. Використовуйте публічний тунель (ngrok або cloudflare) або тестове середовище для повної перевірки потоку CDP.