Mintplex-Labs/anything-llm · error · Error

Token expired.

Error message

Token expired.

What it means

Thrown by TemporaryAuthToken.validate when token.expiresAt < new Date(). Tokens are created with a fixed lifetime (this.expiry). The finally block still deletes the token afterward, so an expired token is also consumed by the failed attempt.

Source

Thrown at server/models/temporaryAuthToken.js:83

   * to be set in the browser localStorage for authentication.
   * @param {string} publicToken - the token to validate against
   * @returns {Promise<{sessionToken: string|null, token: import("@prisma/client").temporary_auth_tokens & {user: import("@prisma/client").users} | null, error: string | null}>}
   */
  validate: async function (publicToken = "") {
    /** @type {import("@prisma/client").temporary_auth_tokens & {user: import("@prisma/client").users} | undefined | null} **/
    let token;

    try {
      if (!publicToken)
        throw new Error(
          "Public token is required to validate a temporary auth token."
        );
      token = await prisma.temporary_auth_tokens.findUnique({
        where: { token: String(publicToken) },
        include: { user: true },
      });
      if (!token) throw new Error("Invalid token.");
      if (token.expiresAt < new Date()) throw new Error("Token expired.");
      if (token.user.suspended) throw new Error("User account suspended.");

      // Create a new session token for the user valid for 30 days
      const sessionToken = makeJWT(
        { id: token.user.id, username: token.user.username },
        process.env.JWT_EXPIRY
      );

      return { sessionToken, token, error: null };
    } catch (error) {
      console.error("FAILED TO VALIDATE TEMPORARY AUTH TOKEN.", error.message);
      return { sessionToken: null, token: null, error: error.message };
    } finally {
      // Delete the token after it has been used under all circumstances if it was retrieved
      if (token)
        await prisma.temporary_auth_tokens.delete({ where: { id: token.id } });
    }
  },

View on GitHub (pinned to 526360e320)

Solutions

  1. Issue a new token; the old one is now deleted and cannot be reused.
  2. If the window is too short for your workflow, increase TemporaryAuthToken.expiry to a larger value (e.g. 1000*60*60 for one hour) and fix the misleading comment.
  3. Deliver SSO links through a channel the user will act on promptly.

Example fix

// before (expiry is ~6 minutes despite the comment)
expiry: 1000 * 60 * 6, // 1 hour
// after
expiry: 1000 * 60 * 60, // 1 hour
Defensive patterns

Strategy: retry

Try / catch

const { error } = await TemporaryAuthToken.validate(token);
if (error === 'Token expired.') {
  const { token: fresh } = await TemporaryAuthToken.issue(userId);
  // send a new link
}

Prevention

When it happens

Trigger: GET /request-token/sso/simple?token=<X> where more than the configured expiry window has elapsed since TemporaryAuthToken.issue created the row.

Common situations: The SSO link sat in an email/chat for too long before the user clicked it. The user's device was offline. Note: the source comment says '1 hour' but this.expiry is 1000*60*6 (6 minutes) — treat the effective window as ~6 minutes unless the constant is corrected.

Understand the failure class

Related errors


AI-assisted analysis of Mintplex-Labs/anything-llm@526360e320 (2026-08-13). Data as JSON: /api/errors/8e8bf68293124c01. Report an issue: GitHub.