Mintplex-Labs/anything-llm · error

Token expired.

Error message

Token expired.

What it means

validate() found the token row but its expiresAt is earlier than the current time (compared against new Date()), so the token is expired and no session JWT is minted. Temporary auth tokens are intentionally short-lived.

Solutions

  1. Issue a new temporary auth token and use it promptly
  2. Regenerate the token close to the moment of delivery, not long before
  3. Verify server time is correct (NTP) so expiresAt comparisons are accurate
  4. Surface a clear 'link expired, request a new one' UX instead of a raw error

Example fix

// before
// generating a token far in advance of the email actually being sent
const link = buildLink(token); // token may expire before user clicks

// after
// generate at send time with a comfortable window
const { token } = await TemporaryAuthToken.create(user.id, hoursFromNow(24));
const link = buildLink(token);
Defensive patterns

Strategy: try-catch

Validate before calling

null // the expiry state lives server-side in temporary_auth_tokens.expiresAt;
// callers cannot reliably pre-check it - handle via the returned error field

Try / catch

const { sessionToken, error } = await TemporaryAuthToken.validate(publicToken);
if (error === 'Token expired.') {
  // transparently re-issue and resend the magic link, then stop this attempt
  const fresh = await issueNewTemporaryAuthToken(user.id);
  return res.status(401).json({ error: 'Link expired. A new one has been sent.', reissued: true });
}

Prevention

When it happens

Trigger: Opening a magic link after the expiry window elapsed; server clock set ahead of the token-creation clock; tokens created long ago and reused; delayed email delivery pushing the click past expiry.

Common situations: Users leaving login links in their inbox overnight; scheduled emails sent with tokens generated earlier in the pipeline; container clocks drifting after host suspension.

Understand the failure class

Related errors


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

Appendix: 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 a145d4d87d)