Kembali ke Beranda
Programming

Membangun Web Chat XMPP yang Aman dengan Java: Jangan Simpan Password XMPP di JavaScript

Aplikasi web chat XMPP tidak sebaiknya menghubungkan browser langsung ke server XMPP menggunakan username dan password. Artikel ini membahas arsitektur yang lebih aman menggunakan Spring Boot sebagai gateway, session cookie HttpOnly, WebSocket, dan koneksi XMPP yang dikelola sepenuhnya di backend.

Pada implementasi web chat XMPP sederhana, browser biasanya langsung terhubung ke server menggunakan JavaScript.

Contohnya:

connection.connect(jid, password, onConnect);

Kode tersebut memang dapat bekerja. Namun, ada pertanyaan penting:

Apakah password XMPP aman jika diproses di JavaScript?

Password pengguna tidak otomatis dapat dicuri oleh pengguna lain hanya karena ada JavaScript di browser. Namun, password tersebut tetap berada di sisi client saat proses login. Jika aplikasi memiliki celah XSS, ekstensi browser berbahaya, perangkat pengguna terinfeksi, atau password disimpan secara keliru di browser, risiko keamanan akan meningkat.

Untuk aplikasi internal, enterprise, atau sistem yang menangani data penting, desain yang lebih baik adalah: browser tidak pernah mengetahui password XMPP.

Arsitektur yang Lebih Aman

Alih-alih browser terhubung langsung ke XMPP, gunakan backend Java sebagai gateway.

Browser
→ HTTPS + Session Login
→ Backend Spring Boot
→ TLS XMPP
→ XMPP Server

Dalam pola ini:

  • Pengguna login ke aplikasi web.
  • Browser menerima session cookie aplikasi.
  • Cookie tidak dapat dibaca JavaScript.
  • Browser berkomunikasi ke backend melalui WebSocket.
  • Backend Java mengirim atau menerima pesan dari server XMPP.
  • Password XMPP hanya tersedia pada server backend.

Dengan demikian, JavaScript tidak lagi memiliki akses terhadap password XMPP.

Perbandingan Dua Pendekatan

AspekBrowser Langsung ke XMPPBackend Java Gateway
Password XMPPDiproses pada browserHanya tersedia di backend
Koneksi XMPPBrowser ke XMPP serverBackend ke XMPP server
Login penggunaAkun XMPP langsungLogin aplikasi atau SSO
Kontrol aksesLebih bergantung pada XMPP serverDapat dikontrol di backend dan XMPP
Audit logTerbatasLebih mudah dicatat
Integrasi bisnisLebih sulitLebih mudah
Cocok untukPrototype sederhanaAplikasi internal dan enterprise

Mengapa Password XMPP Tidak Boleh Ditulis di JavaScript?

Kesalahan yang paling berbahaya adalah menyimpan credential langsung dalam source code.

Contoh yang tidak aman:

const jid = "gateway@chat.example.com";
const password = "password-rahasia";

connection.connect(jid, password, onConnect);

Siapa pun yang membuka source JavaScript di browser dapat melihat credential tersebut.

Hal yang juga perlu dihindari:

localStorage.setItem("xmppPassword", password);

localStorage dapat dibaca oleh JavaScript. Jika ada celah XSS, script berbahaya dapat membaca data tersebut.

Jangan menyimpan password, API key, token layanan, atau credential server di:

  • File JavaScript
  • File HTML
  • Local storage
  • Session storage
  • Repository Git
  • Console log
  • Error message
  • File konfigurasi yang ikut di-deploy ke frontend

Konsep Secure XMPP Gateway

Pada arsitektur gateway, backend memiliki akun XMPP khusus, misalnya:

secure-gateway@chat.nirantara.id

Akun tersebut hanya digunakan oleh aplikasi backend untuk berbicara ke XMPP server.

Credential akun gateway disimpan sebagai environment variable di server:

XMPP_HOST=xmpp-internal.example.com
XMPP_DOMAIN=chat.example.com
XMPP_SERVICE_USER=secure-gateway
XMPP_SERVICE_PASSWORD=password-xmpp-yang-kuat

Backend membaca credential tersebut saat aplikasi berjalan.

String host = System.getenv("XMPP_HOST");
String domain = System.getenv("XMPP_DOMAIN");
String username = System.getenv("XMPP_SERVICE_USER");
String password = System.getenv("XMPP_SERVICE_PASSWORD");

Nilai tersebut tidak dikirim ke browser.

Untuk production, penggunaan secret manager lebih baik daripada memasukkan secret langsung ke file konfigurasi. Contohnya dapat menggunakan Docker Secrets, Kubernetes Secrets, Vault, atau credential manager pada server.

Login Pengguna Menggunakan Spring Security

Pengguna sebaiknya login ke aplikasi, bukan login langsung menggunakan password XMPP.

Spring Security dapat digunakan untuk mengelola autentikasi, session, logout, dan pembatasan halaman.

Contoh konfigurasi sederhana:

@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    return http
            .authorizeHttpRequests(auth -> auth
                    .requestMatchers("/login", "/error").permitAll()
                    .anyRequest().authenticated())
            .formLogin(form -> form.defaultSuccessUrl("/", true))
            .logout(logout -> logout.logoutSuccessUrl("/login?logout"))
            .build();
}

Setelah pengguna berhasil login, Spring Security membuat session aplikasi.

Browser menerima cookie session, tetapi cookie tersebut harus dikonfigurasi dengan aman.

server.servlet.session.cookie.http-only=true
server.servlet.session.cookie.secure=true
server.servlet.session.cookie.same-site=strict

Arti konfigurasi tersebut:

KonfigurasiFungsi
http-only=trueJavaScript tidak dapat membaca cookie session
secure=trueCookie hanya dikirim melalui HTTPS
same-site=strictMembantu mengurangi risiko CSRF lintas situs

Dengan pola ini, frontend tidak perlu menyimpan token atau password XMPP.

WebSocket antara Browser dan Backend

Browser tetap membutuhkan komunikasi real-time untuk chat. Namun, WebSocket yang digunakan adalah WebSocket milik aplikasi backend, bukan koneksi langsung ke server XMPP.

Browser
→ wss://aplikasi.internal/ws/chat
→ Spring Boot WebSocket Handler
→ XMPP Gateway

Contoh JavaScript:

const protocol = location.protocol === 'https:' ? 'wss:' : 'ws:';

const socket = new WebSocket(
    `${protocol}//${location.host}/ws/chat`
);

Karena WebSocket berada pada domain aplikasi yang sama, browser secara otomatis mengirim session cookie yang sudah dimiliki pengguna.

JavaScript tidak perlu membaca cookie tersebut.

socket.onmessage = ({ data }) => {
    const response = JSON.parse(data);

    if (response.type === 'sent') {
        console.log('Pesan berhasil dikirim.');
    }
};

Mengirim Pesan dari Browser ke Backend

Frontend cukup mengirim data yang diperlukan, misalnya JID penerima dan isi pesan.

socket.send(JSON.stringify({
    to: 'operator@chat.example.com',
    body: 'Mohon periksa tiket prioritas tinggi.'
}));

Data tersebut diterima oleh WebSocket handler di Spring Boot.

@Override
protected void handleTextMessage(
        WebSocketSession session,
        TextMessage message
) throws Exception {

    JsonNode request = objectMapper.readTree(
            message.getPayload()
    );

    String recipient = request.path("to").asText().trim();
    String body = request.path("body").asText().trim();

    String sender = session.getPrincipal().getName();

    xmppGateway.send(recipient, body, sender);
}

Perhatikan bagian ini:

String sender = session.getPrincipal().getName();

Backend dapat mengetahui siapa pengguna yang mengirim pesan berdasarkan session login aplikasi, bukan berdasarkan nilai yang dikirim oleh browser.

Jangan mempercayai informasi sender jika nilainya dikirim langsung dari JavaScript, karena pengguna dapat mengubah request melalui browser.

Validasi Pesan dan Penerima

Backend tidak boleh menerima semua input tanpa validasi.

Contoh validasi sederhana untuk penerima:

if (!recipient.matches("^[^@\\s]+@chat\\.example\\.com$")) {
    throw new IllegalArgumentException(
            "Recipient is not allowed"
    );
}

Contoh pembatasan panjang pesan:

if (body.isBlank() || body.length() > 2000) {
    throw new IllegalArgumentException(
            "Message length must be 1-2000 characters"
    );
}

Pada sistem nyata, validasi penerima tidak cukup hanya menggunakan regex domain. Sistem sebaiknya memeriksa:

  • Apakah pengguna memiliki hak chat ke penerima tersebut?
  • Apakah penerima terdaftar dalam organisasi?
  • Apakah pengguna boleh masuk ke group tertentu?
  • Apakah user sedang diblokir?
  • Apakah ada batas jumlah pesan?
  • Apakah pesan berisi data sensitif?

Aturan tersebut dapat dikelola menggunakan database, role, atau RBAC.

Menghubungkan Backend Java ke XMPP Server

Backend Java dapat menggunakan library Smack untuk terhubung ke XMPP server.

XMPPTCPConnectionConfiguration config =
        XMPPTCPConnectionConfiguration.builder()
                .setHost(host)
                .setPort(5222)
                .setXmppDomain(
                        JidCreate.domainBareFrom(domain)
                )
                .setSecurityMode(
                        ConnectionConfiguration.SecurityMode.required
                )
                .build();

connection = new XMPPTCPConnection(config);

connection.connect();
connection.login(username, password);

Bagian berikut penting:

.setSecurityMode(
    ConnectionConfiguration.SecurityMode.required
)

Konfigurasi tersebut meminta koneksi XMPP menggunakan TLS. Jangan menggunakan koneksi XMPP tanpa enkripsi untuk aplikasi production.

Setelah backend berhasil login, pesan dapat dikirim menggunakan ChatManager.

Chat chat = ChatManager.getInstanceFor(connection)
        .chatWith(JidCreate.entityBareFrom(recipient));

chat.send(body);

Satu Akun Gateway atau Akun XMPP per Pengguna?

Ada dua pola yang dapat dipilih.

1. Satu Akun XMPP Gateway

Backend menggunakan satu akun service:

secure-gateway@chat.nirantara.id

Keunggulannya:

  • Credential lebih mudah dikelola
  • Browser tidak menyimpan password XMPP
  • Lebih mudah membuat audit log
  • Cocok untuk bot, notifikasi, dan sistem internal

Kekurangannya:

  • Penerima akan melihat pesan berasal dari akun gateway
  • Identitas pengguna aplikasi perlu dimasukkan sebagai metadata

Contoh:

[demo] Mohon periksa tiket prioritas tinggi.

2. Akun XMPP per Pengguna

Setiap pengguna memiliki akun XMPP masing-masing, tetapi autentikasi tetap dilakukan secara aman melalui backend atau identity provider.

Pola ini cocok jika sistem membutuhkan:

  • Presence per pengguna
  • Pesan benar-benar muncul dari akun pengguna
  • Group chat berbasis identitas individual
  • Riwayat dan device management XMPP

Untuk pola ini, gunakan mekanisme autentikasi modern seperti SASL OAuth/OIDC jika didukung oleh server XMPP. Alternatif lain adalah menyimpan credential pengguna di secret vault pada server, tetapi desain tersebut harus dilakukan dengan sangat hati-hati.

Pengamanan Tambahan untuk Production

Selain memindahkan password XMPP ke backend, beberapa pengamanan berikut perlu diterapkan.

Gunakan HTTPS dan WSS

Akses aplikasi menggunakan:

https://chat-aplikasi.example.com

Dan WebSocket:

wss://chat-aplikasi.example.com/ws/chat

Hindari http:// dan ws:// di production.

Terapkan Rate Limiting

Batasi jumlah pesan yang dapat dikirim pengguna dalam periode tertentu.

Contohnya:

Maksimal 30 pesan per menit per pengguna

Rate limiting membantu mengurangi spam, abuse, dan serangan otomatis.

Buat Audit Log

Catat aktivitas penting seperti:

  • User login
  • User logout
  • Koneksi WebSocket
  • Pesan dikirim
  • Penerima pesan
  • Waktu kejadian
  • Error koneksi XMPP
  • Penolakan akses

Untuk data sensitif, jangan menyimpan isi pesan penuh tanpa kebutuhan bisnis yang jelas.

Hindari Pesan Error Terlalu Detail

Jangan mengirim detail error server ke browser.

Kurang aman:

XMPP authentication failed for secure-gateway because password is invalid.

Lebih aman:

Pesan tidak dapat dikirim. Silakan coba kembali.

Detail teknis tetap dapat disimpan pada log backend yang aman.

Gunakan Reconnect dengan Backoff

Koneksi backend ke XMPP dapat terputus karena jaringan atau restart server.

Backend perlu memiliki mekanisme reconnect, tetapi jangan melakukan percobaan koneksi secara terus-menerus tanpa jeda.

Gunakan pendekatan exponential backoff:

1 detik
→ 2 detik
→ 4 detik
→ 8 detik
→ maksimal 60 detik

Penutup

Menghubungkan browser langsung ke XMPP memang cepat untuk membuat prototype. Namun, untuk aplikasi yang lebih aman, password XMPP sebaiknya tidak pernah diproses atau disimpan di JavaScript.

Gunakan backend Java sebagai secure gateway:

Browser → Session Aplikasi → Backend Java → XMPP Server

Dengan desain ini, browser hanya berinteraksi dengan aplikasi melalui HTTPS dan WebSocket. Password XMPP, aturan akses, audit log, validasi pesan, serta integrasi sistem dikelola di backend.

Pendekatan ini membuat aplikasi chat lebih siap untuk digunakan pada lingkungan internal, proses bisnis, notifikasi operasional, bot, maupun integrasi AI assistant.

xmppjavaspring bootwebsocketsecuritysecure chatsmackopenfireejabberdbackend developmentweb developmentsession security
Nirantara Logo

Published by Nirantara

Nirantara is an Indonesian Enterprise AI company specializing in Enterprise AI, RAG, Intelligent Search, and AI Automation. Article written by jsturana.

Learn more about Nirantara