Budibase/budibase · error

Unexpected token payload

Error message

Unexpected token payload

What it means

jwt.verify normally returns either a string (when the JWT is an unsecured/unencoded payload or parsed as JWS with a string body) or a decoded object. This plain Error is thrown when jsonwebtoken returns a string payload instead of an object, meaning the token body is not the JSON object with claims (email, etc.) that embed SSO requires. The library only accepts object payloads because it reads claims like the configured emailClaim from the payload.

Source

Thrown at packages/server/src/sdk/workspace/embedSSO/index.ts:85

): string | undefined => {
  const value = emailClaim
    .split(".")
    .reduce<any>((acc, part) => (acc == null ? acc : acc[part]), payload)
  return typeof value === "string" ? value : undefined
}

const verifyToken = (
  token: string,
  config: EmbedSSOConfig
): Record<string, any> => {
  const key = decodeSecret(config.key)
  const options: jwt.VerifyOptions = { algorithms: [config.algorithm] }
  if (config.issuer) {
    options.issuer = config.issuer
  }
  const decoded = jwt.verify(token, key, options)
  if (typeof decoded === "string") {
    throw new Error("Unexpected token payload")
  }
  return decoded
}

/**
 * Verify a signed token from the embedding host, map its identity to an
 * existing Budibase user and, if found, establish a Budibase session by setting
 * the auth cookie. Returns true if the user was authenticated.
 */
export async function authenticateEmbedUser(
  ctx: Ctx,
  config: EmbedSSOConfig,
  token: string
): Promise<boolean> {
  let payload: Record<string, any>
  try {
    payload = verifyToken(token, config)
  } catch (err) {

View on GitHub (pinned to a81a902e9a)

Solutions

  1. Fix the token producer to sign a JSON object payload containing at least the email claim, e.g. jwt.sign({ email: user.email }, key, { algorithm: config.algorithm })
  2. Verify the embedding host's signing code uses the same key and algorithm configured in the embed SSO config
  3. If tokens come from a third party that only produces string payloads, decode/transform them into an object before sending, or map a custom emailClaim path after signing an object

Example fix

// before (host side)
const token = jwt.sign(user.id, sharedSecret) // string payload
// after
const token = jwt.sign({ email: user.email }, sharedSecret, { algorithm: "HS256" })
Defensive patterns

Strategy: try-catch

Validate before calling

const headerB64 = token.split(".")[1]
const payload = JSON.parse(Buffer.from(headerB64, "base64url").toString())
if (typeof payload !== "object" || payload === null || Array.isArray(payload)) {
  throw new Error("Token payload must be a JSON object")
}

Type guard

const isJwtPayload = (v: unknown): v is Record<string, any> =>
  typeof v === "object" && v !== null && !Array.isArray(v)

Try / catch

let payload: Record<string, any>
try {
  payload = verifyToken(token, config)
} catch (err) {
  if (err.message === "Unexpected token payload") {
    // token was signed with a non-object payload — reject and re-authenticate
    return false
  }
  throw err
}

Prevention

When it happens

Trigger: Authenticating an embed user with a token whose decoded payload is a plain string — e.g. a token built with jsonwebtoken.sign("some-string", secret) rather than sign(payloadObject, secret), or a hand-crafted/opaque token that happens to pass signature verification.

Common situations: The embedding host signs a raw string or non-JSON payload instead of a claims object; an old or incompatible SDK on the host side emits string tokens; a proxy or test harness sends an arbitrary opaque token signed with the shared secret.

Understand the failure class

Related errors


AI-assisted analysis of Budibase/budibase@a81a902e9a (2026-08-29). Data as JSON: /api/errors/bd6f9f1f21a712a9. Report an issue: GitHub.