# Session Token

> 嵌入式 Shoplazza 应用如何用 Session Token 鉴权：JWT 的各段结构、一分钟有效期，以及后端验签的做法。

Shoplazza 后台中的[嵌入式应用](/docs/app/getting-started/app-types#embedded-apps-method)使用 OAuth 和 session token 进行身份验证。本指南适用于嵌入式应用的开发。

## Session token 的工作原理

Session token 也称为 [JSON web token（JWT）](https://jwt.io)，用于让您的应用对客户端与后端之间发出的请求进行身份验证。Session token 中还包含当前使用您的嵌入式应用的商家信息。

## 使用 session token 的身份验证流程

当您的[嵌入式应用](/docs/app/getting-started/app-types#embedded-apps-method)首次加载时，处于未认证状态，此时会向用户提供应用的前端代码。应用会向用户渲染一个界面骨架或加载画面。

前端代码加载完成后，应用会调用 [Shoplazza App Bridge action](/docs/app/developer-tools/app-bridge/actions/) 获取 session token。在向后端发起任何 HTTPS 请求时，应用会在授权请求头中携带该 session token。

![](https://cnres.appracle.com/8633e4b77dfc8a6d37968580568ef363.png "截屏2021-12-27 下午4.37.34.png")

## 使用 session token 的请求流程

Session token 使用您的应用与 Shoplazza 之间的共享密钥进行签名，以便您的后端验证请求是否有效。

![](https://cnres.appracle.com/0ed2a5b4fd5a64fb867a4ed3aac3caec.png "截屏2021-12-28 下午1.25.44.png")

## Session token 的有效期

Session token 的有效期为一分钟。每次请求时必须通过 Shoplazza App Bridge 重新获取 session token，以确保不使用过期的 token。

## Session token 的结构

Session token 由 header、payload 和签名三部分组成。如需交互式示例，可参考 [JWT.io](https://jwt.io)，在那里您可以尝试为各部分设置不同的值。Shoplazza 建议您在 JWT.io 上测试时使用测试应用的凭证。

**Header**\
Header 中的值是固定的，不会更改。

```json
{
  "alg": "HS256",
  "typ": "JWT"
}
```

* alg：用于编码 JWT 的算法。
* typ：session token 用于声明媒体类型的（type）header 参数。

**Payload**

```json
{
  "iss": "<shop-name.myshoplaza.com/admin>",
  "dest": "<shop-name.myshoplaza.com>",
  "aud": "<app client id>",
  "sub": "<user ID>",
  "exp": "<time in seconds>",
  "nbf": "<time in seconds>",
  "iat": "<time in seconds>",
  "jti": "<random UUID>",
  "sid": "<session ID>",
  "locale": "zh-CN",
  "account":"test@shoplazza.com"
}
```

* iss：商店的后台域名。
* dest：商店的域名。
* aud：接收方应用的 API key。
* sub：session token 的目标用户。
* exp：session token 过期的时间（秒）。
* nbf：session token 生效的时间（秒）。
* iat：session token 签发的时间（秒）。
* jti：安全随机 UUID。
* sid：每个用户和应用的唯一 session ID。
* locale：商店的语言区域（语言和地区），例如 zh-CN 表示中文。
* account：用户登录账号。

**Payload 示例**

```json
{
  "locale": "zh-CN",
  "account": "test@shoplazza.com",
  "dest": "test.myshoplaza.com",
  "sid": "MTY0MDIyMzE5MHxRaHMzanN1OF9leGdWQTNYZmdqS2tvcnQ0UXpmVlhrZVlhZlJSSG1URTBnOUY4WFNVdl9BVWVmNHozbkVnYU5yc3NwRG9MZFptSGs9fPCmLb7qbttCuZl79rEcRKho9lRqTLZsvs_OESW0um8I",
  "aud": "825a8255676252ee1053073b2b42528c763fd011972ad2803036aea89882920c",
  "exp": 1640331670,
  "jti": "1cf4b3dd-6ccc-4978-9c5a-ad9cee17d4a7",
  "iat": 1640331610,
  "iss": "https://test.myshoplaza.com/admin",
  "nbf": 1640331610,
  "sub": "dafd283d-1274-4412-b86d-21a68ab1172f"
}
```

## 在后端验证 session token

后端在信任请求之前，必须对每个请求验证 session token。验证使用签发 token 时所用的同一个客户端密钥（`CLIENT_SECRET`）。

1. 从 `Authorization: Bearer <token>` 请求头读取 token。
2. 用 `CLIENT_SECRET` 以 `HS256` 算法验证签名和 `exp` 过期时间。
3. 从已验证的 payload 中读取店铺和用户信息，而不是从请求参数中读取。

示例（Node.js，`jsonwebtoken`）：

```javascript
const jwt = require("jsonwebtoken");

function verifySessionToken(req, res, next) {
  const authHeader = req.headers.authorization;
  if (!authHeader || !authHeader.startsWith("Bearer ")) {
    return res.status(401).json({ error: "Missing session token" });
  }

  const token = authHeader.replace("Bearer ", "");

  try {
    // jwt.verify 会校验签名和 exp 过期时间
    const decoded = jwt.verify(token, CLIENT_SECRET, { algorithms: ["HS256"] });
    req.session = {
      shop: decoded.dest, // 店铺域名——可信
      userId: decoded.sub, // 用户 ID
      locale: decoded.locale,
      account: decoded.account,
    };
    next();
  } catch (err) {
    return res.status(401).json({ error: "Invalid session token" });
  }
}
```

:::note
店铺应从已验证的 `dest` 字段解出，而不是从 `shop` URL 参数获取。URL 参数可被伪造，已验证的 token 不能。
:::

使用该中间件的完整实战教程，见 [开发嵌入式应用](/docs/app/getting-started/create-public-app/embed-in-admin)。
