Skip to content

Authentication

The Lovrabet SDK supports several authentication methods to cover the security needs of different scenarios. This guide walks through configuring and using each one.

💡 Prerequisite Before you start, make sure the SDK is configured. Letting the CLI generate the configuration automatically is recommended.

Authentication methods at a glance

The SDK supports the following authentication methods:

  1. User token authentication - for scenarios where the user is already logged in
  2. OpenAPI key authentication - for server-to-server calls
  3. Cookie authentication (automatic) - for browser environments with a logged-in user
  4. Runtime switching - update credentials dynamically

👤 User token authentication

TypeScript
import { registerModels, createClient } from "@lovrabet/sdk";

// 先注册基础配置(推荐使用 CLI 自动生成)
registerModels({
  appCode: "my-app",
  models: [
    { datasetCode: "8d2dcbae08b54bdd84c00be558ed48df", tableName: "users", alias: "users" },
    { datasetCode: "a1b2c3d4e5f6789012345678abcdef12", tableName: "orders", alias: "orders" },
  ],
});

// 创建带认证的客户端
const client = createClient({
  token: "your-user-token",
});

// 标准方式访问
const users = await client.models.dataset_8d2dcbae08b54bdd84c00be558ed48df.filter();
// 或使用别名(语法糖)
const users = await client.models.users.filter();

Option 2: pass the configuration directly

TypeScript
const client = createClient({
  appCode: "my-app",
  token: "your-user-token",
  models: [
    { datasetCode: "8d2dcbae08b54bdd84c00be558ed48df", tableName: "users", alias: "users" },
    { datasetCode: "a1b2c3d4e5f6789012345678abcdef12", tableName: "orders", alias: "orders" },
  ],
});

// 标准方式访问
const users = await client.models.dataset_8d2dcbae08b54bdd84c00be558ed48df.filter();

Practical example

TypeScript
// auth/client.ts
import { createClient } from "@lovrabet/sdk";
import "./api/api"; // 导入 CLI 生成的配置
import { getAuthToken } from "./auth-service";

// 创建认证客户端工厂
export const createAuthenticatedClient = () => {
  const token = getAuthToken(); // 从存储中获取 token

  return createClient({
    token,
    options: {
      onError: (error) => {
        if (error.message.includes("unauthorized")) {
          // Token 失效,重定向到登录页
          window.location.href = "/login";
        }
      },
    },
  });
};

// 使用
const client = createAuthenticatedClient();
// 标准方式访问
const users = await client.models.dataset_8d2dcbae08b54bdd84c00be558ed48df.filter();
// 或使用别名
const users = await client.models.users.filter();

🔑 OpenAPI key authentication

OpenAPI key authentication is designed for server-to-server calls and provides stronger security.

🚨 Security warning: do not use accessKey in the browser

⚠️ accessKey must only be used on the server!

Using accessKey in a browser environment poses serious security risks:

The correct approach:

Authentication flow comparison:

Secure server-side proxy pattern

The recommended pattern is to create an API proxy on the server that gives the frontend secure access to data:

TypeScript
// 服务器端:使用 accessKey
import { createClient } from '@lovrabet/sdk';

const serverClient = createClient({
  accessKey: process.env.LOVRABET_ACCESS_KEY,
});

// API 路由:/api/users
export async function GET(request: Request) {
  // 验证用户身份(从 session/cookie)
  const user = await validateUser(request);
  if (!user) {
    return Response.json({ error: 'Unauthorized' }, { status: 401 });
  }

  // 使用服务器端 client 获取数据
  const users = await serverClient.models.dataset_8d2dcbae08b54bdd84c00be558ed48df.filter();
  return Response.json(users);
}

// 浏览器端:调用服务器 API
fetch('/api/users')
  .then(res => res.json())
  .then(data => console.log(data));
TypeScript
// 先注册基础配置(推荐使用 CLI 自动生成)
registerModels({
  appCode: "my-app",
  models: [
    { datasetCode: "8d2dcbae08b54bdd84c00be558ed48df", tableName: "users", alias: "users" },
    { datasetCode: "a1b2c3d4e5f6789012345678abcdef12", tableName: "analytics", alias: "analytics" },
  ],
});

// 创建带密钥认证的客户端
const client = createClient({
  accessKey: "your-access-key",
  secretKey: "your-secret-key",
});

// 标准方式访问
const users = await client.models.dataset_8d2dcbae08b54bdd84c00be558ed48df.filter();

Option 2: pass the configuration directly

TypeScript
const client = createClient({
  appCode: "my-app",
  accessKey: "your-access-key",
  secretKey: "your-secret-key",
  models: [
    { datasetCode: "8d2dcbae08b54bdd84c00be558ed48df", tableName: "users", alias: "users" },
    { datasetCode: "a1b2c3d4e5f6789012345678abcdef12", tableName: "analytics", alias: "analytics" },
  ],
});

// 标准方式访问
const analytics = await client.models.dataset_a1b2c3d4e5f6789012345678abcdef12.filter();

Server-side example

TypeScript
// server/lovrabet-client.ts
import { registerModels, createClient } from "@lovrabet/sdk";

registerModels({
  appCode: process.env.LOVRABET_APP_CODE,
  models: [
    {
      datasetCode: process.env.USERS_DATASET_CODE,
      tableName: "users",
      alias: "users",
    },
    {
      datasetCode: process.env.ORDERS_DATASET_CODE,
      tableName: "orders",
      alias: "orders",
    },
  ],
});

// 使用环境变量中的密钥
export const serverClient = createClient({
  accessKey: process.env.LOVRABET_ACCESS_KEY,
  secretKey: process.env.LOVRABET_SECRET_KEY,
});

// API 路由中使用
export async function GET() {
  try {
    // 使用环境变量中的 datasetCode
    const users = await serverClient.models[`dataset_${process.env.USERS_DATASET_CODE}`].filter({
      currentPage: 1,
      pageSize: 100,
    });

    return Response.json(users);
  } catch (error) {
    console.error("获取用户数据失败:", error);
    return Response.json({ error: "Internal Server Error" }, { status: 500 });
  }
}

If no token or keys are provided, the SDK automatically authenticates with the browser's cookies. This fits browser scenarios where the user is already logged in.

TypeScript
import { createClient } from "@lovrabet/sdk";
import "./api/api"; // 导入 CLI 生成的配置

// 不提供任何认证信息,自动使用 Cookie
const client = createClient();

// 如果用户已登录,会自动使用浏览器中的登录 Cookie
// 标准方式访问
const users = await client.models.dataset_8d2dcbae08b54bdd84c00be558ed48df.filter();
// 或使用别名
const users = await client.models.users.filter();

Hybrid authentication strategy

TypeScript
// 优先使用 Token,回退到 Cookie
const createSmartClient = (token?: string) => {
  const config = token ? { token } : {}; // 有 token 就用,没有就依赖 Cookie

  return createClient(config);
};

// 使用
const tokenFromStorage = localStorage.getItem("auth_token");
const client = createSmartClient(tokenFromStorage);

🔄 Switching authentication at runtime

The SDK can update credentials at runtime — no need to recreate the client.

Set a token dynamically

TypeScript
const client = createClient(); // 初始无认证

// 用户登录后,动态设置 token
client.setToken("new-user-token");

// 现在可以访问需要认证的 API
const users = await client.models.users.filter();

// 用户登出时,清除 token
client.setToken(null);

A realistic login flow

TypeScript
// auth/auth-manager.ts
import { createClient } from "@lovrabet/sdk";
import "./api"; // 导入预注册的配置

class AuthManager {
  private client = createClient(); // 初始化客户端

  async login(username: string, password: string) {
    try {
      // 调用登录 API
      const response = await fetch("/api/auth/login", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ username, password }),
      });

      if (response.ok) {
        const { token } = await response.json();

        // 设置 token 到 SDK
        this.client.setToken(token);

        // 存储到本地
        localStorage.setItem("auth_token", token);

        return { success: true };
      } else {
        return { success: false, error: "登录失败" };
      }
    } catch (error) {
      return { success: false, error: "网络错误" };
    }
  }

  logout() {
    // 清除 SDK 中的 token
    this.client.setToken(null);

    // 清除本地存储
    localStorage.removeItem("auth_token");

    // 重定向到登录页
    window.location.href = "/login";
  }

  getClient() {
    return this.client;
  }

  // 初始化时从本地存储恢复 token
  init() {
    const token = localStorage.getItem("auth_token");
    if (token) {
      this.client.setToken(token);
    }
  }
}

export const authManager = new AuthManager();

// 应用启动时初始化
authManager.init();

// 导出客户端供业务代码使用
export const lovrabetClient = authManager.getClient();

React hook example

TypeScript
// hooks/useAuth.ts
import { useState, useEffect } from "react";
import { authManager, lovrabetClient } from "../auth/auth-manager";

export function useAuth() {
  const [isAuthenticated, setIsAuthenticated] = useState(false);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    // 检查认证状态
    const checkAuth = async () => {
      try {
        // 尝试获取用户信息来验证认证状态
        await lovrabetClient.models.users.getOne("me");
        setIsAuthenticated(true);
      } catch (error) {
        setIsAuthenticated(false);
      } finally {
        setLoading(false);
      }
    };

    checkAuth();
  }, []);

  const login = async (username: string, password: string) => {
    const result = await authManager.login(username, password);
    if (result.success) {
      setIsAuthenticated(true);
    }
    return result;
  };

  const logout = () => {
    authManager.logout();
    setIsAuthenticated(false);
  };

  return {
    isAuthenticated,
    loading,
    login,
    logout,
    client: lovrabetClient,
  };
}

🛡️ Security best practices

1. Token storage security

TypeScript
// 安全的 Token 存储管理
class SecureTokenStorage {
  private readonly TOKEN_KEY = "lovrabet_auth_token";

  setToken(token: string) {
    // 生产环境建议使用 httpOnly cookie 或其他安全存储方案
    if (process.env.NODE_ENV === "production") {
      // 设置安全的 cookie
      document.cookie = `${this.TOKEN_KEY}=${token}; secure; httpOnly; samesite=strict`;
    } else {
      // 开发环境使用 localStorage
      localStorage.setItem(this.TOKEN_KEY, token);
    }
  }

  getToken(): string | null {
    if (process.env.NODE_ENV === "production") {
      // 从 cookie 中读取
      const cookies = document.cookie.split(";");
      const tokenCookie = cookies.find(c => c.trim().startsWith(this.TOKEN_KEY));
      return tokenCookie ? tokenCookie.split("=")[1] : null;
    } else {
      return localStorage.getItem(this.TOKEN_KEY);
    }
  }

  removeToken() {
    if (process.env.NODE_ENV === "production") {
      document.cookie = `${this.TOKEN_KEY}=; expires=Thu, 01 Jan 1970 00:00:00 GMT`;
    } else {
      localStorage.removeItem(this.TOKEN_KEY);
    }
  }
}

2. Managing environment variables

TypeScript
// .env.example
LOVRABET_APP_CODE=your-app-code
LOVRABET_ACCESS_KEY=your-access-key
LOVRABET_SECRET_KEY=your-secret-key

// config/lovrabet.ts
export const lovrabetConfig = {
  appCode: process.env.LOVRABET_APP_CODE!,
  accessKey: process.env.LOVRABET_ACCESS_KEY,
  secretKey: process.env.LOVRABET_SECRET_KEY,
};

// 验证必要的环境变量
if (!lovrabetConfig.appCode) {
  throw new Error("LOVRABET_APP_CODE is required");
}

3. Error handling and retries

TypeScript
const client = createClient({
  token: "user-token",
  options: {
    timeout: 30000,
    onError: (error) => {
      console.error("Lovrabet SDK Error:", {
        message: error.message,
        status: error.status,
        url: error.url,
      });

      // 根据错误类型进行不同处理
      if (error.status === 401) {
        // 认证失败
        console.log("Authentication failed, redirecting to login...");
        authManager.logout();
      } else if (error.status >= 500) {
        // 服务器错误,可以考虑重试
        console.log("Server error, may retry later");
      }
    },
  },
});

🔍 Debugging authentication issues

Inspect the current configuration

TypeScript
const client = createClient({ token: "your-token" });

// 获取当前配置(不包含敏感信息)
const config = client.getConfig();
console.log("当前配置:", {
  appCode: config.appCode,
  env: config.env,
  hasToken: !!config.token,
  hasAccessKey: !!config.accessKey,
  serverUrl: config.serverUrl,
});

Check whether authentication is valid

TypeScript
// 检测认证是否有效
const checkAuthStatus = async (client) => {
  try {
    // 尝试调用一个需要认证的 API
    await client.models.users.filter({ pageSize: 1 });
    console.log("✅ 认证有效");
    return true;
  } catch (error) {
    if (error.status === 401) {
      console.log("❌ 认证无效或已过期");
    } else {
      console.log("❓ 其他错误:", error.message);
    }
    return false;
  }
};

📖 Next steps

With authentication configured, you can continue with:

  • 📊 API guide - dive deeper into API operations
  • 🎯 TypeScript support - a type-safe development experience
  • 🛠️ Practical examples - React/Vue integration examples

❓ FAQ

Q: What is the difference between a token and OpenAPI keys?

  • Token: user-level authentication, suited to frontend apps and user-scoped operations
  • OpenAPI keys: app-level authentication, suited to server-to-server calls, with higher privileges

Q: How do I handle token expiration?

TypeScript
const client = createClient({
  token: "initial-token",
  options: {
    onError: async (error) => {
      if (error.status === 401) {
        // Token 可能过期,尝试刷新
        try {
          const newToken = await refreshToken();
          client.setToken(newToken);
          // 可以选择重试原始请求
        } catch (refreshError) {
          // 刷新失败,跳转到登录页
          window.location.href = "/login";
        }
      }
    },
  },
});

Q: How do I use the SDK in Node.js?

For Node.js environments, OpenAPI key authentication is recommended:

TypeScript
// server/lovrabet.js
const { registerModels, createClient } = require("@lovrabet/sdk");

registerModels({
  appCode: process.env.LOVRABET_APP_CODE,
  models: [
    { datasetCode: process.env.USERS_DATASET_CODE, tableName: "users", alias: "users" },
  ],
});

const client = createClient({
  accessKey: process.env.LOVRABET_ACCESS_KEY,
  secretKey: process.env.LOVRABET_SECRET_KEY,
});

module.exports = { client };

Next steps

  • 📋 Configuration - learn about the various configuration methods
  • 📊 API guide - dive deeper into API operations
  • 🛠️ Practical examples - React/Vue integration examples

基于飞书知识库同步生成,内容以飞书源文档为准