Authentication guide
A deep dive into Lovrabet OpenAPI authentication: how it works, token management, and best practices.
Authentication overview
Lovrabet OpenAPI authenticates requests with HMAC-SHA256 signatures. Depending on where your code runs and what it accesses, there are three modes:
| Mode | Use case | Credential required |
|---|---|---|
| Server-side mode | Node.js servers, SSR | accessKey |
| Browser token mode | Public data access from the browser | Pre-generated token + timestamp |
| Browser Cookie mode | Private data for signed-in users | None (uses Cookies) |
TIP
The SDK handles it The official SDK picks the right mode from the options you pass — no manual selection needed.
Credentials
What you need
| Credential | Description | How to get it | Example |
|---|---|---|---|
| App Code | Unique app identifier | Contact your account manager | app-c2dd52a2 |
| Access Key | Access key | Contact your account manager | ak-xxxxxxxxxxxxx |
| Dataset Code | Dataset identifier | Contact your account manager | 0fefba76fe29...ff |
INFO
Security note The Access Key is a secret. Store it safely — never leak it or hard-code it into your source.
Mode 1: Server-side authentication (accessKey)
For Node.js servers, Next.js SSR, API routes, and similar environments.
Basic usage
import { createClient } from "@lovrabet/sdk";
const client = createClient({
appCode: "your-app-code",
accessKey: process.env.LOVRABET_ACCESS_KEY, // ✅ 从环境变量读取
models: {
users: {
tableName: "users",
datasetCode: "your-dataset-code",
},
},
});
// SDK 自动生成 Token 并处理认证
const users = await client.models.users.filter();How it works
When you provide an accessKey:
The SDK takes the
accessKeyand the current timestampIt generates a token on the fly with HMAC-SHA256
It adds the authentication headers to every request:
X-App-Code: app identifierX-Dataset-Code: dataset identifierX-Time-Stamp: current timestampX-Token: the generated signature
Next.js Server Component example
// app/users/page.tsx (Server Component)
import { createClient } from "@lovrabet/sdk";
// 在服务端组件中创建客户端
const client = createClient({
appCode: process.env.LOVRABET_APP_CODE!,
accessKey: process.env.LOVRABET_ACCESS_KEY!,
models: {
users: {
tableName: "users",
datasetCode: process.env.LOVRABET_DATASET_CODE!,
},
},
});
export default async function UsersPage() {
// 直接在服务端调用
const { tableData: users } = await client.models.users.filter();
return (
<div>
{users.map((user) => (
<div key={user.id}>{user.name}</div>
))}
</div>
);
}Environment variables
Create a .env.local file:
LOVRABET_APP_CODE=your-app-code
LOVRABET_ACCESS_KEY=your-access-key
LOVRABET_DATASET_CODE=your-dataset-codeTIP
Security best practices
- ✅ Store the Access Key in environment variables
- ✅ Add
.env.localto.gitignore - ✅ Use different keys per environment
- ❌ Never hard-code keys in your source
Mode 2: Browser token authentication (pre-generated)
For public data access from the browser, anonymous users, and similar scenarios.
INFO
Important Never use an accessKey in browser code! Generate the token on the server and pass it to the browser.
Step 1: Generate the token on the server
Generate a token with generateOpenApiToken():
// app/api/token/route.ts
import { generateOpenApiToken } from "@lovrabet/sdk";
import { NextResponse } from "next/server";
export async function GET() {
try {
const result = await generateOpenApiToken({
appCode: process.env.LOVRABET_APP_CODE!,
datasetCode: process.env.LOVRABET_DATASET_CODE!,
accessKey: process.env.LOVRABET_ACCESS_KEY!,
// secretKey: "lovrabet", // 可选,默认为 "lovrabet"
// timestamp: Date.now(), // 可选,默认为当前时间
});
// result 包含: { token, timestamp, expiresAt }
return NextResponse.json(result);
} catch (error) {
return NextResponse.json({ error: "Token 生成失败" }, { status: 500 });
}
}Return structure:
{
token: string; // 生成的 Token
timestamp: number; // 时间戳(毫秒)
expiresAt: Date; // 过期时间
}Step 2: Use the token in the browser
// app/users/client-page.tsx
"use client";
import { createClient } from "@lovrabet/sdk";
import { useEffect, useState } from "react";
export default function ClientUsersPage() {
const [users, setUsers] = useState([]);
useEffect(() => {
async function init() {
// 1. 从服务端获取 Token
const { token, timestamp } = await fetch("/api/token").then((r) =>
r.json()
);
// 2. 创建客户端(使用 token 和 timestamp)
const client = createClient({
appCode: "your-app-code",
token: token, // 预生成的 token
timestamp: timestamp, // 对应的时间戳
models: {
users: { tableName: "users", datasetCode: "your-dataset-code" },
},
});
// 3. 查询数据
const { tableData } = await client.models.users.filter();
setUsers(tableData);
}
init();
}, []);
return (
<div>
{users.map((user) => (
<div key={user.id}>{user.name}</div>
))}
</div>
);
}How it works
When you provide token and timestamp:
The SDK uses the pre-generated token as-is
No signature is computed at request time
It sends these headers:
X-Token: the pre-generated tokenX-Time-Stamp: its matching timestampX-App-CodeandX-Dataset-Code
Token lifetime management
Tokens are valid for 10 minutes (600 seconds); refresh them once they expire.
Check whether a token is about to expire
import { isTokenExpiring, getTokenRemainingTime } from "@lovrabet/sdk";
const timestamp = Date.now();
// 检查是否即将过期(默认缓冲时间 1 分钟)
if (isTokenExpiring(timestamp)) {
console.log("Token 即将过期,需要刷新");
}
// 检查是否即将过期(自定义缓冲时间 2 分钟)
if (isTokenExpiring(timestamp, 120000)) {
console.log("2 分钟内将过期");
}
// 获取剩余时间(毫秒)
const remaining = getTokenRemainingTime(timestamp);
console.log(`Token 剩余 ${remaining / 1000} 秒`);Refresh tokens automatically
"use client";
import {
createClient,
isTokenExpiring,
getTokenRemainingTime,
} from "@lovrabet/sdk";
import { useEffect, useState } from "react";
export default function UsersWithAutoRefresh() {
const [client, setClient] = useState(null);
const [timestamp, setTimestamp] = useState(null);
const [remainingTime, setRemainingTime] = useState(null);
// 初始化
useEffect(() => {
fetchTokenAndCreateClient();
}, []);
// 定期检查 Token 有效期
useEffect(() => {
if (!timestamp) return;
const interval = setInterval(() => {
const remaining = getTokenRemainingTime(timestamp);
setRemainingTime(remaining);
// 提前 1 分钟刷新
if (isTokenExpiring(timestamp, 60000)) {
console.log("Token 即将过期,刷新中...");
fetchTokenAndCreateClient();
}
}, 10000); // 每 10 秒检查一次
return () => clearInterval(interval);
}, [timestamp]);
async function fetchTokenAndCreateClient() {
const { token, timestamp: newTimestamp } = await fetch("/api/token").then(
(r) => r.json()
);
const newClient = createClient({
appCode: "your-app-code",
token: token,
timestamp: newTimestamp,
models: {
users: { tableName: "users", datasetCode: "your-dataset-code" },
},
});
setClient(newClient);
setTimestamp(newTimestamp);
}
// 显示剩余时间
return (
<div>
<p>Token 剩余: {Math.floor((remainingTime || 0) / 1000)} 秒</p>
{/* ... 其他内容 */}
</div>
);
}Generate tokens in bulk
To mint tokens for several datasets at once, use the TokenGenerator class:
import { TokenGenerator } from "@lovrabet/sdk";
const generator = new TokenGenerator(
process.env.LOVRABET_ACCESS_KEY!,
"lovrabet" // secretKey,可选
);
// 批量生成
const tokens = await generator.generateBatch({
appCode: "your-app-code",
datasets: [
{ name: "users", code: "dataset-001" },
{ name: "orders", code: "dataset-002" },
{ name: "products", code: "dataset-003" },
],
// timestamp: Date.now(), // 可选,默认当前时间
});
// 使用生成的 tokens
console.log(tokens.users.token);
console.log(tokens.users.timestamp);
console.log(tokens.users.expiresAt);
console.log(tokens.orders.token);
console.log(tokens.products.token);
// 返回一个 API 端点
export async function GET() {
return NextResponse.json(tokens);
}TokenGenerator methods
class TokenGenerator {
constructor(accessKey: string, secretKey?: string);
// 生成单个 Token
generate(params: {
appCode: string;
datasetCode: string;
timestamp?: number;
}): Promise<TokenResult>;
// 批量生成 Token
generateBatch(params: {
appCode: string;
datasets: Array<{ name: string; code: string }>;
timestamp?: number;
}): Promise<Record<string, TokenResult>>;
}Mode 3: Browser Cookie authentication
For signed-in users reading their own private data.
Usage
"use client";
import { createClient } from "@lovrabet/sdk";
// 不提供任何认证信息
const client = createClient({
appCode: "your-app-code",
models: {
orders: { tableName: "orders", datasetCode: "your-dataset-code" },
},
});
// 请求会自动携带浏览器的登录 Cookie
const { tableData } = await client.models.orders.filter();How it works
When no accessKey or token is provided:
- The SDK switches to WebAPI mode
- Requests automatically carry the browser's Cookies
- Authentication rides on the user's login session
- Ideal for signed-in users accessing their own data
Good fits
- ✅ A user's order list
- ✅ Profile data
- ✅ Favorites and shopping carts
- ❌ Public data for anonymous users
Choosing a mode
Decision tree
Running on the server?
├─ Yes → use accessKey mode (Mode 1)
└─ No (browser)
├─ User signed in and reading private data?
│ ├─ Yes → use Cookie mode (Mode 3)
│ └─ No → use pre-generated token mode (Mode 2)
└─ Accessing public data?
└─ Yes → use pre-generated token mode (Mode 2)Comparison
| Feature | Server-side mode | Token mode | Cookie mode |
|---|---|---|---|
| Environment | Node.js server | Browser | Browser |
| Credential | accessKey | token + timestamp | Cookie |
| Token generation | On the fly | Pre-generated | No token needed |
| Validity | Unlimited | 10 minutes | Tied to the login session |
| Security | Highest | High | Medium |
| Best for | SSR, API routes | Public data access | Private data access |
Error handling
Authentication error codes
| Code | Description | Solution |
|---|---|---|
| 1002 | Signature verification failed | Check the Access Key |
| 1003 | Timestamp expired | The token has expired; generate a new one |
| 1004 | App not found | Check the App Code |
| 1005 | Dataset not found | Check the Dataset Code |
| 1006 | Access denied | Confirm the app has access to the dataset |
Error handling example
import { LovrabetError } from "@lovrabet/sdk";
try {
const users = await client.models.users.filter();
} catch (error) {
if (error instanceof LovrabetError) {
switch (error.statusCode) {
case 1002:
console.error("签名验证失败,请检查 Access Key");
break;
case 1003:
console.error("Token 已过期,正在刷新...");
// 重新获取 token
await fetchTokenAndCreateClient();
break;
case 1006:
console.error("无权访问该数据集");
break;
default:
console.error("API 错误:", error.message);
}
} else {
console.error("未知错误:", error);
}
}Best practices
1. Credential security
INFO
Key security rules
- ✅ Server: keep the Access Key in environment variables
- ✅ Browser: use a pre-generated token; never expose the Access Key
- ✅ Rotate the Access Key regularly
- ✅ Use different keys per environment
- ❌ Never hard-code an Access Key in client code
- ❌ Never commit an Access Key to a repository
Wrong:
// ❌ 危险!会暴露 Access Key
const client = createClient({
accessKey: "ak-xxxxx", // 不要这样做!
});Correct:
// ✅ 服务端:使用环境变量
const client = createClient({
accessKey: process.env.LOVRABET_ACCESS_KEY,
});
// ✅ 浏览器:使用预生成 Token
const { token } = await fetch("/api/token").then((r) => r.json());
const client = createClient({ token });2. Reuse client instances
// ✅ 推荐:复用客户端实例
const client = createClient({
/* 配置 */
});
const users = await client.models.users.filter();
const orders = await client.models.orders.filter();
// ❌ 避免:重复创建客户端
const client1 = createClient({
/* 配置 */
});
const users = await client1.models.users.filter();
const client2 = createClient({
/* 配置 */
}); // 不必要
const orders = await client2.models.orders.filter();3. Token refresh strategy
// 提前刷新,避免过期
if (isTokenExpiring(timestamp, 60000)) {
// 提前 1 分钟刷新
await refreshToken();
}
// 错误时重试
try {
await api.call();
} catch (error) {
if (error.statusCode === 1003) {
// Token 过期,刷新后重试
await refreshToken();
await api.call();
}
}Advanced usage
Update a token at runtime
const client = createClient({
appCode: "your-app-code",
models: {
/* ... */
},
});
// 后续更新 token
client.setToken(newToken, newTimestamp);Custom request options
const client = createClient({
appCode: "your-app-code",
accessKey: process.env.LOVRABET_ACCESS_KEY,
models: {
/* ... */
},
options: {
timeout: 30000, // 30 秒超时
// 其他 fetch 选项
},
});Signature algorithm in depth (advanced)
INFO
For reference only The SDK generates signatures for you; you normally never need this level of detail.
Steps to generate a signature:
- Collect the parameters:
- Sort alphabetically and concatenate:
- Compute the signature with HMAC-SHA256:
- Add it to the request headers:
FAQ
Q: Why use the SDK?
A: It wraps every authentication detail for you:
- ✅ Automatic signature generation
- ✅ Timestamp handling
- ✅ Token lifecycle management
- ✅ Error handling and retries
- ✅ TypeScript types
Q: How long is a token valid?
A: 10 minutes (600 seconds). Refresh 1-2 minutes early to stay safe.
Q: How do I use it safely in the browser?
A: Never ship an accessKey to the browser. Instead:
- Generate the token server-side
- Return it to the frontend via your API
- Consume the pre-generated token in the browser
Q: What if my Access Key leaks?
A: Act immediately:
- Ask your account manager for a new Access Key
- Revoke the old one
- Update every app that used the old key
- Audit potentially affected data
Q: Can I customize the token lifetime?
A: No — 10 minutes is fixed by design for security reasons.
Next steps
- API reference - full endpoint documentation
- Quick start - learn by example
Need help?
For authentication issues:
- Check the error handling section in this document
- Browse GitHub Issues
- Contact your account manager for technical support