TuBrief
구독 채널
비디오
커뮤니티

Cara Pengembang TypeScript Menangani Autentikasi WebSocket dan Verifikasi Otorisasi Saat Menerapkan Aplikasi SkyBridge

TuBrief 편집팀
2026년 8월 19일
0
Computing/Software

원본 영상을 바탕으로 AI의 도움을 받아 작성했습니다. 원본 영상이 기준입니다.

Bahasa Indonesia한국어EnglishEspañol中文العربيةहिन्दीDeutschFrançaisPortuguêsРусский日本語

관련 영상

Aplikasi MCP Mengubah Internet. Begini Cara Membuatnya! (Skybridge)9:21

Aplikasi MCP Mengubah Internet. Begini Cara Membuatnya! (Skybridge)

Better Stack

커뮤니티의 다른 글

사내 시스템에 llm api 붙일 때 마주하는 현실적인 한계와 대응법

2026년 9월 13일

레거시 백엔드에 GPT-6 Astra 붙일 때 예산 승인과 보안 통과를 먼저 끝내는 법이 있습니다

2026년 9월 13일

에이전트끼리 대화하다 6천만 원 청구서가 나오는 이유

2026년 9월 13일

사내 RAG 벡터 검색에 Okta 권한 필터를 직접 거는 방법

2026년 9월 13일

브라우저 에이전트에게 내 구글 계정을 통째로 넘기면 안 되는 이유

2026년 9월 12일

Apple Won the AI Race

2026년 9월 12일

댓글 (0)

Log in to leave a comment

아직 작성된 글이 없습니다

© 2026 . All rights reserved.

TuBrief
구독 채널
비디오
커뮤니티
로그인

Cara Pengembang TypeScript Menangani Autentikasi WebSocket dan Verifikasi Otorisasi Saat Menerapkan Aplikasi SkyBridge

Saat beralih dari endpoint REST tradisional ke runtime SkyBridge berbasis Model Context Protocol, komunikasi waktu nyata dua arah antara browser dan backend sangatlah penting. Handshake WebSocket dasar mengekspos token autentikasi, memicu konflik status ketika agen AI dan pengguna campur tangan secara bersamaan, serta menghabiskan memori server karena penyambungan ulang yang sering. Artikel ini membahas cara menerapkan keamanan lapisan transport, kontrol akses berbasis peran (RBAC) yang terperinci, penyelesaian konflik, dan tata kelola memori yang diperlukan untuk menerapkan aplikasi MCP SkyBridge secara aman di lingkungan enterprise.

1. Keamanan Sinkronisasi Status Waktu Nyata untuk Mencegah Pembajakan WebSocket

API WebSocket browser standar tidak mendukung pengaturan kustom header selama permintaan peningkatan HTTP awal. Jika pengembang meneruskan JWT sebagai parameter kueri, token akan tetap berada dalam teks biasa di reverse proxy, load balancer, dan riwayat browser, sehingga mengeksponensialkan risiko pembajakan sesi. Selain itu, WebSocket memotong kebijakan Same-Origin browser, sehingga rentan terhadap serangan di mana situs web berbahaya membajak soket menggunakan hak istimewa pengguna yang diautentikasi kecuali asal (origin) diverifikasi secara ketat.

Untuk mengatasi hal ini, Anda harus membangun protokol autentikasi dua langkah dan verifikasi tanda tangan HMAC tingkat paket.

  1. Penerbitan Tiket Sekali Pakai (One-Time Ticket): Klien meminta tiket WebSocket sekali pakai dengan masa berlaku 10 detik yang terikat pada sesi pengguna dan IP melalui endpoint REST.
  2. Pengiriman Header Protokol: Selama handshake WebSocket, tiket dikirimkan dalam header protokol, dan server langsung menghapusnya dari penyimpanan setelah verifikasi untuk mencegah serangan replay.
  3. Penandatanganan dan Verifikasi Paket Status: Setiap kali klien mengirimkan perubahan status, tanda tangan dibuat dengan menggabungkan kunci sesi, payload, dan timestamp milidetik yang meningkat secara monoton. Server melakukan perbandingan byte waktu konstan.

Membangun pipeline verifikasi ini sepenuhnya mencegah serangan timing oracle melalui analisis byte dan mencegah modifikasi status yang tidak sah, sehingga menghemat waktu debugging kerentanan sesi WebSocket setelah deployment selama lebih dari 15 jam.

`typescript
import { createServer, IncomingMessage } from 'http';
import { WebSocketServer, WebSocket } from 'ws';
import { createHmac, timingSafeEqual } from 'crypto';

interface SkyBridgeSessionContext {
userId: string;
tenantId: string;
roles: string[];
sessionKey: Buffer;
connectionId: string;
}

interface AuthenticatedWebSocket extends WebSocket {
context?: SkyBridgeSessionContext;
isAlive?: boolean;
}

interface SignedStatePacket {
payload: Record<string, unknown>;
timestamp: number;
signature: string;
}

const ticketRegistry = new Map<string, { userId: string; tenantId: string; roles: string[]; sessionKey: Buffer; expiresAt: number }>();
const server = createServer();
const wss = new WebSocketServer({ noServer: true });

server.on('upgrade', (request: IncomingMessage, socket, head) => {
const origin = request.headers.origin;
const allowedOrigins = ['https://chatgpt.com', 'https://enterprise.internal.app'];

if (!origin || !allowedOrigins.includes(origin)) {
socket.write('HTTP/1.1 403 Forbidden\r\n\r\n');
socket.destroy();
return;
}

const subprotocols = request.headers['sec-websocket-protocol']?.split(',').map(s => s.trim()) || [];
const ticketProtocol = subprotocols.find(p => p.startsWith('ticket.'));

if (!ticketProtocol) {
socket.write('HTTP/1.1 401 Unauthorized\r\n\r\n');
socket.destroy();
return;
}

const ticket = ticketProtocol.replace('ticket.', '');
const ticketData = ticketRegistry.get(ticket);

if (!ticketData || ticketData.expiresAt < Date.now()) {
ticketRegistry.delete(ticket);
socket.write('HTTP/1.1 401 Unauthorized\r\n\r\n');
socket.destroy();
return;
}

ticketRegistry.delete(ticket);

wss.handleUpgrade(request, socket, head, (ws: AuthenticatedWebSocket) => {
ws.context = {
userId: ticketData.userId,
tenantId: ticketData.tenantId,
roles: ticketData.roles,
sessionKey: ticketData.sessionKey,
connectionId: crypto.randomUUID()
};
ws.isAlive = true;
wss.emit('connection', ws, request, ticketProtocol);
});
});

function verifyPacketSignature(ws: AuthenticatedWebSocket, rawData: string): SignedStatePacket | null {
if (!ws.context) return null;

try {
const packet: SignedStatePacket = JSON.parse(rawData);
const { payload, timestamp, signature } = packet;

if (Math.abs(Date.now() - timestamp) > 5000) return null;

const messageBuffer = Buffer.from(`${JSON.stringify(payload)}:${timestamp}`);
const computedHmac = createHmac('sha256', ws.context.sessionKey).update(messageBuffer).digest();
const providedSignatureBuffer = Buffer.from(signature, 'hex');

if (computedHmac.length !== providedSignatureBuffer.length) return null;
return timingSafeEqual(computedHmac, providedSignatureBuffer) ? packet : null;

} catch {
return null;
}
}

wss.on('connection', (ws: AuthenticatedWebSocket) => {
ws.on('message', (message: string) => {
const verifiedPacket = verifyPacketSignature(ws, message.toString());
if (!verifiedPacket) {
ws.send(JSON.stringify({ error: 'INVALID_PACKET_SIGNATURE', code: 4003 }));
ws.close(4003, 'Signature verification failed');
return;
}
});
});

`


2. Penerapan Kontrol Akses Berbasis Peran di Dalam Komponen UI Interaktif

SkyBridge menggunakan tipe MIME tertentu untuk merender widget iframe sandbox di dalam antarmuka interaktif. Jika cakupan izin (scope) yang dikirimkan dari backend tidak dimasukkan secara langsung ke dalam tahap rendering awal komponen, pengguna yang tidak memiliki izin dapat mengeklik tombol pemicu dan menghasilkan lalu lintas jaringan yang tidak perlu serta kesalahan keamanan. Klaim izin pengguna yang disertakan dalam bidang meta harus diteruskan ke konteks klien melalui antarmuka output alat (tool output) dari lingkungan host.

Prosedur untuk membangun guard verifikasi izin dan pipeline pembaruan token tanpa gangguan adalah sebagai berikut:

  1. Inisialisasi Konteks Keamanan: Saat widget dimount, ekstrak array cakupan pengguna dan pasok ke konteks keamanan React.
  2. Penempatan Guard Aksi Proaktif: Bungkus semua tombol atau bidang input yang memerlukan izin dengan komponen guard untuk mengaktifkan atribut disabled dan tooltip panduan secara otomatis jika izin tidak memadai.
  3. Penanganan Pemulihan Sesi Mulus: Saat menerima frame kontrol kedaluwarsa sesi selama komunikasi soket, alih-alih menutup soket, kirimkan pesan ke window induk untuk memperbarui tiket di latar belakang.

Dengan menerapkan metode ini, Anda dapat melestarikan sesi secara utuh tanpa mengganggu alur percakapan atau mereset data formulir yang sedang ditulis.

`typescript
import React, { createContext, useContext, useEffect, useState } from 'react';

interface SecurityContextType {
userId: string;
scopes: string[];
hasScope: (scope: string) => boolean;
}

const SecurityContext = createContext<SecurityContextType | null>(null);

export const SecurityProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
const [context, setContext] = useState<SecurityContextType | null>(null);

useEffect(() => {
const toolOutput = (window as unknown as { openai?: { toolOutput?: { _meta?: { userScopes?: string[]; userId?: string } } } }).openai?.toolOutput;
const userScopes = toolOutput?._meta?.userScopes || [];
const userId = toolOutput?._meta?.userId || 'anonymous';

setContext({
  userId,
  scopes: userScopes,
  hasScope: (requiredScope: string) => userScopes.includes(requiredScope) || userScopes.includes('admin:*')
});

}, []);

if (!context) return

Initializing Security Context...
;
return <SecurityContext.Provider value={context}>{children}</SecurityContext.Provider>;
};

export const useSecurity = () => {
const ctx = useContext(SecurityContext);
if (!ctx) throw new Error('useSecurity must be used within a SecurityProvider');
return ctx;
};

export const ActionGuard: React.FC<{ requiredScope: string; children: React.ReactElement }> = ({ requiredScope, children }) => {
const { hasScope } = useSecurity();
const isAllowed = hasScope(requiredScope);

return React.cloneElement(children, {
disabled: !isAllowed || children.props.disabled,
'data-permission-granted': isAllowed,
title: isAllowed ? children.props.title : 'Unauthorized: Insufficient enterprise permissions'
});
};

`


3. Penanganan Modifikasi Status Bersamaan dan Race Condition untuk Sesi Multi-Pengguna

Ketidakcocokan data yang serius terjadi ketika seseorang yang memanipulasi kartu inline dan agen AI yang secara otonom memanggil alat MCP memodifikasi entitas yang sama secara bersamaan. Pendekatan timestamp yang mengandalkan jam absolut sistem tidak menjamin urutan status secara akurat karena clock skew dan latensi antar server terdistribusi.

Untuk mempertahankan konsistensi data, algoritma yang menggabungkan Hybrid Logical Clock (HLC) dan vector clock digunakan. Tupel terdiri dari waktu fisik dan penghitung logis; jika waktu fisik sama, penghitung logis dibandingkan, dan jika masih sama, pengidentifikasi node unik dibandingkan untuk menyelesaikan konflik secara deterministik.

Langkah-langkah untuk membangun pembaruan UI optimis dan mekanisme rollback guna menurunkan latensi yang dirasakan di bawah 50 milidetik adalah sebagai berikut:

  1. Pembuatan Snapshot Status: Ketika peristiwa input pengguna terjadi, buat salinan mendalam (deep copy) dari status saat ini dan daftarkan ke peta antrean (pending map).
  2. Pengiriman Antrean Microtask: Segera perbarui status lokal dan vector clock untuk mencerminkan perubahan pada layar, lalu jadwalkan pengiriman pesan soket secara asinkron melalui microtask.
  3. Penanganan Respons Server dan Rollback: Saat menerima respons kegagalan verifikasi backend, terapkan kembali permintaan perubahan yang tertunda secara berurutan di atas status server standar untuk pemulihan normal.

Melalui manajemen status optimis ini, latensi yang bergantung pada waktu perjalanan pulang-pergi (RTT) jaringan dapat dipangkas hingga di bawah 45 milidetik, menghasilkan peningkatan kecepatan respons lebih dari 75 persen.

`typescript
export interface VersionVector { [nodeId: string]: number; }
export interface HybridTimestamp { millis: number; counter: number; nodeId: string; }
export interface EnterpriseStateEntity { id: string; data: T; versionVector: VersionVector; hlcTimestamp: HybridTimestamp; }
export interface MutationRequest { entityId: string; mutatedData: Partial; vector: VersionVector; hlcTimestamp: HybridTimestamp; mutationId: string; }

export class OptimisticStateManager<T extends { id: string }> {
private canonicalState: EnterpriseStateEntity;
private optimisticState: EnterpriseStateEntity;
private pendingMutations: Map<string, { snapshot: EnterpriseStateEntity; request: MutationRequest }> = new Map();

constructor(initialState: EnterpriseStateEntity) {
this.canonicalState = structuredClone(initialState);
this.optimisticState = structuredClone(initialState);
}

public getSnapshot(): EnterpriseStateEntity {
return this.optimisticState;
}

public applyOptimisticMutation(mutation: MutationRequest, dispatchWebSocketMessage: (req: MutationRequest) => void): void {
const snapshot = structuredClone(this.optimisticState);
this.pendingMutations.set(mutation.mutationId, { snapshot, request: mutation });

this.optimisticState.data = { ...this.optimisticState.data, ...mutation.mutatedData };
this.optimisticState.versionVector[mutation.hlcTimestamp.nodeId] = 
  (this.optimisticState.versionVector[mutation.hlcTimestamp.nodeId] || 0) + 1;

queueMicrotask(() => dispatchWebSocketMessage(mutation));

}

public handleServerResponse(response: { mutationId: string; success: boolean; canonicalServerState?: EnterpriseStateEntity }): void {
const pending = this.pendingMutations.get(response.mutationId);
if (!pending) return;

this.pendingMutations.delete(response.mutationId);
if (response.canonicalServerState) {
  this.canonicalState = structuredClone(response.canonicalServerState);
}

if (!response.success) {
  this.rebuildOptimisticState();
}

}

private rebuildOptimisticState(): void {
let base = structuredClone(this.canonicalState);
for (const [, { request }] of this.pendingMutations) {
base.data = { ...base.data, ...request.mutatedData };
base.versionVector[request.hlcTimestamp.nodeId] =
(base.versionVector[request.hlcTimestamp.nodeId] || 0) + 1;
}
this.optimisticState = base;
}
}

`


4. Pencegahan Kebocoran Memori dan Profiling Heap pada Koneksi Berjalan Lama (Long-Running)

Instance server SkyBridge mengalami siklus penyambungan ulang WebSocket yang sering karena perpindahan tab, masuk ke mode hemat daya browser, dan lainnya. Jika event listener tidak secara eksplisit dilepaskan (unregistered) saat soket ditutup atau konteks soket dipertahankan di dalam closure, garbage collector mesin browser gagal mengumpulkan instance tersebut, sehingga menyebabkan kebocoran memori.

Prosedur untuk mencegah kebocoran memori dan memverifikasinya secara otomatis selama fase CI/CD adalah sebagai berikut:

  1. Implementasi Pengelola Langganan WeakRef: Gunakan referensi lemah (weak reference) saat menyimpan saluran langganan dan hubungkan FinalizationRegistry agar objek soket sepenuhnya dihapus dari daftar saluran saat menjadi target garbage collection.
  2. Pelepasan Sumber Daya Soket Eksplisit: Batalkan langganan ketika peristiwa penutupan soket klien terjadi, dan hapus saluran itu sendiri ketika ukuran peta saluran mencapai 0.
  3. Uji Diff Snapshot Heap: Panggil penggunaan memori dalam test suite untuk memverifikasi apakah tingkat pertumbuhan memori heap setelah 1.000 kali penyambungan ulang berturut-turut berada di bawah 1 persen.

`typescript
import { describe, it, expect } from 'vitest';
import { getHeapSnapshot } from 'v8';
import { WebSocket } from 'ws';

function captureHeapAllocatedBytes(): number {
if (global.gc) global.gc();
getHeapSnapshot();
return process.memoryUsage().heapUsed;
}

describe('SkyBridge WebSocket Reconnection Memory Governance', () => {
it('should maintain heap memory growth under 1% threshold after 1,000 reconnection cycles', async () => {
const SERVER_URL = 'ws://localhost:8080';
const TEST_CYCLES = 1000;

const baselineMemory = captureHeapAllocatedBytes();

for (let i = 0; i < TEST_CYCLES; i++) {
  await new Promise<void>((resolve) => {
    const ws = new WebSocket(SERVER_URL, ['ticket.test_eph_ticket_id']);
    ws.on('open', () => {
      ws.send(JSON.stringify({ type: 'PING' }));
      ws.terminate();
    });
    ws.on('close', () => resolve());
  });
}

const postTestMemory = captureHeapAllocatedBytes();
const memoryGrowthPercentage = ((postTestMemory - baselineMemory) / baselineMemory) * 100;

expect(memoryGrowthPercentage).toBeLessThan(1.0);

}, 60000);
});

`

Item Metrik Lapisan Transport Standar Tanpa Optimasi Runtime SkyBridge yang Dioptimalkan Hasil Peningkatan
Memori Heap untuk 10.000 Koneksi Bersamaan 840 MB 546 MB Penggunaan RAM server berkurang 35%
Latensi Pembaruan Status UI Lokal 180 ms hingga 320 ms Kurang dari 45 ms Latensi operasional yang dirasakan berkurang > 75%
Event Loop Lag Saat Lonjakan Penyambungan Ulang Rata-rata penundaan 85 ms per siklus Rata-rata penundaan 4 ms Penghapusan total pemblokiran event loop
Profil Keamanan Autentikasi Risiko eksposur token log URL Nol eksposur token & verifikasi waktu konstan Memenuhi arsitektur Zero Trust

Dengan memperkenalkan struktur manajemen langganan referensi lemah dan pipeline uji diff heap otomatis, Anda dapat menurunkan pangsa memori heap server untuk 10.000 koneksi bersamaan dari 840 MB menjadi 546 MB (penurunan sebesar 35 persen). Bahkan dalam situasi lonjakan penyambungan ulang, jeda event loop dapat dikurangi secara signifikan untuk mempertahankan layanan enterprise yang stabil di bawah lalu lintas skala besar.