คีย์ชั่วคราว
ห้ามเปิดเผย secret_key ที่มีอายุยาวนานในโค้ดฝั่งเบราว์เซอร์
สำหรับการผสานรวมบนเว็บ เบราว์เซอร์ควรได้รับเฉพาะคีย์ชั่วคราวที่มีอายุสั้น ในทางปฏิบัติ พาร์ทเนอร์จำนวนมากจะสร้าง endpoint ขนาดเล็กบนเซิร์ฟเวอร์ เช่น POST /api/ephemeral-key ซึ่ง endpoint นี้จะเรียก SORI Console API จาก ฝั่งเซิร์ฟเวอร์
เหตุผลที่ควรใช้คีย์ชั่วคราว
ในการผสานรวมบนเว็บ ผู้ใช้สามารถเข้าถึงโค้ดฝั่งเบราว์เซอร์ได้ หากใส่ secret_key ที่มีอายุยาวนานไว้ในโค้ดดังกล่าว คีย์อาจถูกดึงออกไปและนำไปใช้ ในทางที่ผิดนอกบริการของคุณ
คีย์ชั่วคราวช่วยป้องกันปัญหานี้ เซิร์ฟเวอร์ของคุณจะเก็บ secret_key ที่มีอายุยาวนานไว้เป็นความลับ แลกคีย์ดังกล่าวเป็นคีย์ชั่วคราวที่มีอายุสั้น และส่งเฉพาะคีย์ชั่วคราวนั้นกลับไปยังเบราว์เซอร์ จากนั้นเบราว์เซอร์สามารถใช้ คีย์ชั่วคราวเพื่อยืนยันตัวตนและเรียก SDK API ต่อไปนี้ โดยไม่เปิดเผยคีย์ลับเดิม
วิธีใช้งานฝั่งไคลเอนต์ที่แนะนำ
โดยทั่วไป AudioRecognizer ควรรับคีย์ผ่าน ephemeralKey
1. ดึงคีย์ภายใน ephemeralKey
import { AudioRecognizer } from "@sorisdk/web-audio";
const recognizer = new AudioRecognizer({
appId: "YOUR_APP_ID",
ephemeralKey: async () => {
const response = await fetch("/api/ephemeral-key", {
method: "POST"
});
if (!response.ok) {
throw new Error(`Ephemeral key request failed: HTTP ${response.status}`);
}
const { ephemeral_key } = await response.json();
return ephemeral_key;
}
});
recognizer.on("campaign", (event) => {
console.log(event.campaign);
});
await recognizer.start();2. ดึงคีย์ก่อน แล้วส่งให้โดยตรง
import { AudioRecognizer } from "@sorisdk/web-audio";
const response = await fetch("/api/ephemeral-key", {
method: "POST"
});
if (!response.ok) {
throw new Error(`Ephemeral key request failed: HTTP ${response.status}`);
}
const { ephemeral_key } = await response.json();
const recognizer = new AudioRecognizer({
appId: "YOUR_APP_ID",
ephemeralKey: ephemeral_key
});
recognizer.on("campaign", (event) => {
console.log(event.campaign);
});
await recognizer.start();การสร้าง endpoint สำหรับแลกคีย์ของคุณเอง
หากคุณดูแล endpoint ของตนเอง หน้าที่ของ endpoint มีดังนี้:
- รับคำขอจากเบราว์เซอร์ เช่น
POST /api/ephemeral-key - อ่าน
app_idและsecret_keyจากการกำหนดค่าฝั่งเซิร์ฟเวอร์ - เรียก endpoint สำหรับคีย์ชั่วคราวของ SORI Console API บนเซิร์ฟเวอร์
- ส่ง payload การตอบกลับที่มีอายุสั้นนั้นกลับไปยังเบราว์เซอร์
เมื่อใช้ base URL เริ่มต้นของ SORI API endpoint ต้นทางคือ:
https://console.soriapi.com/api/auth/ephemeralเนื้อหาคำขอต้นทางเข้ารหัสแบบฟอร์ม:
app_id=YOUR_APP_ID
secret_key=YOUR_SECRET_KEYendpoint ของคุณต้องไม่ส่งคืน secret_key ที่มีอายุยาวนาน
การตอบกลับของ SORI Console API
ใน sori_console เมื่อ POST /api/auth/ephemeral สำเร็จ จะส่งคืนข้อมูลในรูปแบบต่อไปนี้:
{
"success": true,
"ephemeral_key": "eyJhbGciOi...",
"ephemeral_key_expires_at": "2026-03-23T12:34:56Z"
}success: ผลลัพธ์ของคำขอephemeral_key: คีย์ที่มีอายุสั้นสำหรับ SDK ฝั่งเบราว์เซอร์ephemeral_key_expires_at: เวลาหมดอายุในรูปแบบ ISO 8601
หน้าที่ของเซิร์ฟเวอร์
- เก็บ
app_idและsecret_keyไว้ในตัวแปรสภาพแวดล้อมฝั่งเซิร์ฟเวอร์หรือพื้นที่จัดเก็บข้อมูลลับ - เรียก SORI Console API ด้วยข้อมูลที่เข้ารหัสแบบฟอร์ม
- หากการตอบกลับจากต้นทางไม่สำเร็จ ให้ส่งคืนสถานะดังกล่าวและหยุดการทำงาน
- ส่งเฉพาะ payload การตอบกลับที่มีอายุสั้นกลับไปยังเบราว์เซอร์
ตัวอย่างการนำไปใช้
ตัวอย่างต่อไปนี้ใช้ขั้นตอนการแลกคีย์เดียวกันทั้งหมด:
- Node.js:
POST /api/ephemeral-key - Python:
POST /api/ephemeral-key - Java:
POST /api/ephemeral-key - PHP:
POST /api/sori_ephemeral_key.php - การเรียกต้นทาง:
POST https://console.soriapi.com/api/auth/ephemeral - การตอบกลับไปยังเบราว์เซอร์: รูปแบบ JSON เดียวกับ SORI Console API
import express from "express";
const app = express();
const APP_ID = process.env.SORI_APP_ID;
const SECRET_KEY = process.env.SORI_SECRET_KEY;
const SORI_EPHEMERAL_ENDPOINT = "https://console.soriapi.com/api/auth/ephemeral";
app.post("/api/ephemeral-key", async (_req, res) => {
if (!APP_ID || !SECRET_KEY) {
res.status(500).json({ detail: "Missing SORI_APP_ID or SORI_SECRET_KEY" });
return;
}
try {
const upstream = await fetch(SORI_EPHEMERAL_ENDPOINT, {
method: "POST",
body: new URLSearchParams({
app_id: APP_ID,
secret_key: SECRET_KEY
})
});
const raw = await upstream.text();
if (!upstream.ok) {
res.status(upstream.status).type("application/json").send(raw);
return;
}
res.type("application/json").send(raw);
} catch (error) {
res.status(500).json({
detail: error instanceof Error ? error.message : String(error)
});
}
});
app.listen(3000, () => {
console.log("Listening on http://localhost:3000");
});import os
import requests
from fastapi import FastAPI
from fastapi.responses import JSONResponse, Response
app = FastAPI()
APP_ID = os.getenv("SORI_APP_ID")
SECRET_KEY = os.getenv("SORI_SECRET_KEY")
SORI_EPHEMERAL_ENDPOINT = "https://console.soriapi.com/api/auth/ephemeral"
@app.post("/api/ephemeral-key")
def create_sori_ephemeral_key():
if not APP_ID or not SECRET_KEY:
return JSONResponse(
status_code=500,
content={"detail": "Missing SORI_APP_ID or SORI_SECRET_KEY"},
)
try:
upstream = requests.post(
SORI_EPHEMERAL_ENDPOINT,
data={
"app_id": APP_ID,
"secret_key": SECRET_KEY,
},
timeout=10,
)
except requests.RequestException as exc:
return JSONResponse(status_code=500, content={"detail": str(exc)})
if not upstream.ok:
return Response(
content=upstream.text,
status_code=upstream.status_code,
media_type="application/json",
)
return Response(content=upstream.text, media_type="application/json")package example.sori;
import com.fasterxml.jackson.databind.ObjectMapper;
import okhttp3.FormBody;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Map;
@RestController
public class SoriEphemeralKeyController {
private final OkHttpClient httpClient = new OkHttpClient();
private final ObjectMapper objectMapper = new ObjectMapper();
private final String appId = System.getenv("SORI_APP_ID");
private final String secretKey = System.getenv("SORI_SECRET_KEY");
private static final String SORI_EPHEMERAL_ENDPOINT =
"https://console.soriapi.com/api/auth/ephemeral";
@PostMapping(value = "/api/ephemeral-key", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<String> createEphemeralKey() {
if (isBlank(appId) || isBlank(secretKey)) {
return json(500, Map.of(
"detail", "Missing SORI_APP_ID or SORI_SECRET_KEY"
));
}
RequestBody body = new FormBody.Builder()
.add("app_id", appId)
.add("secret_key", secretKey)
.build();
Request request = new Request.Builder()
.url(SORI_EPHEMERAL_ENDPOINT)
.post(body)
.build();
try (Response response = httpClient.newCall(request).execute()) {
String rawBody = response.body() != null ? response.body().string() : "{}";
if (!response.isSuccessful()) {
return ResponseEntity.status(response.code())
.contentType(MediaType.APPLICATION_JSON)
.body(rawBody);
}
return ResponseEntity.ok()
.contentType(MediaType.APPLICATION_JSON)
.body(rawBody);
} catch (Exception error) {
return json(500, Map.of("detail", error.getMessage()));
}
}
private ResponseEntity<String> json(int status, Map<String, String> body) {
try {
return ResponseEntity.status(status)
.contentType(MediaType.APPLICATION_JSON)
.body(objectMapper.writeValueAsString(body));
} catch (Exception error) {
return ResponseEntity.status(status)
.contentType(MediaType.APPLICATION_JSON)
.body("{\"detail\":\"Unexpected server error\"}");
}
}
private static boolean isBlank(String value) {
return value == null || value.isBlank();
}
}<?php
// /api/sori_ephemeral_key.php
header('Content-Type: application/json');
$appId = getenv('SORI_APP_ID');
$secretKey = getenv('SORI_SECRET_KEY');
$soriEphemeralEndpoint = 'https://console.soriapi.com/api/auth/ephemeral';
if (!$appId || !$secretKey) {
http_response_code(500);
echo json_encode(['detail' => 'Missing SORI_APP_ID or SORI_SECRET_KEY']);
exit;
}
$curl = curl_init($soriEphemeralEndpoint);
curl_setopt_array($curl, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => http_build_query([
'app_id' => $appId,
'secret_key' => $secretKey,
]),
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Content-Type: application/x-www-form-urlencoded',
],
]);
$raw = curl_exec($curl);
$statusCode = curl_getinfo($curl, CURLINFO_RESPONSE_CODE);
if ($raw === false) {
http_response_code(500);
echo json_encode(['detail' => curl_error($curl)]);
curl_close($curl);
exit;
}
curl_close($curl);
if ($statusCode < 200 || $statusCode >= 300) {
http_response_code($statusCode);
echo $raw;
exit;
}
echo $raw;หมายเหตุ
- เบราว์เซอร์ควรเรียกเฉพาะ endpoint สำหรับคีย์ที่มีอายุสั้นของคุณ ไม่ใช่เส้นทางจัดการคีย์ลับที่มีอายุยาวนาน
- หากคุณโฮสต์แอป SORI หลายแอป ตัวส่งต่อสามารถเลือกค่า
app_idที่แตกต่างกันตามคำขอ เทนเนนต์ หรือสภาพแวดล้อมการนำไปใช้งาน - หากต้องใช้ middleware หรือการยืนยันตัวตนเฉพาะเฟรมเวิร์ก ให้สัญญาการเชื่อมต่อกับเบราว์เซอร์เรียบง่ายและเสถียร
