nocobase/nocobase · error
Invalid temporary file access token
Error message
Invalid temporary file access token
What it means
verifyTemporaryFileAccessToken() verifies a temporary-file-access JWT with HS256 against the plugin-derived secret and the expected audience (TEMPORARY_FILE_ACCESS_AUDIENCE). If jwt.verify passes (signature/expiry valid) but the decoded payload is a string or falsy — i.e. not the expected JSON object payload — it throws 'Invalid temporary file access token'. Note that jwt.verify itself throws separately for bad signatures and expired tokens; this error specifically means the token decoded but its shape is not a valid TemporaryFileAccessPayload object.
Source
Thrown at packages/plugins/@nocobase/plugin-file-manager/src/server/temporary-access.ts:91
...(resource.role ? { role: resource.role } : {}),
},
deriveTemporaryFileAccessSecret(plugin),
{
algorithm: 'HS256',
audience: TEMPORARY_FILE_ACCESS_AUDIENCE,
...(currentUserId ? { subject: String(currentUserId) } : {}),
expiresIn: options.expiresIn || getTemporaryFileAccessExpiresIn(),
},
);
}
export function verifyTemporaryFileAccessToken(plugin: PluginFileManagerServer, token: string) {
const decoded = jwt.verify(token, deriveTemporaryFileAccessSecret(plugin), {
algorithms: ['HS256'],
audience: TEMPORARY_FILE_ACCESS_AUDIENCE,
});
if (!decoded || typeof decoded === 'string') {
throw new Error('Invalid temporary file access token');
}
return decoded as TemporaryFileAccessPayload;
}
export async function createTemporaryURLAction(ctx: Context, next: Next) {
const collectionName = ctx.action.resourceName;
const collection = ctx.dataSource.collectionManager.getCollection(collectionName) as Collection | undefined;
if (!isFileCollection(collection)) {
return ctx.throw(404);
}
if (!hasStandardFileId(collection)) {
ctx.logger.error('file collection is missing standard id field', {
method: 'file-manager.createTemporaryURL',
collection: collection.name,
});
return ctx.throw(500);
}
View on GitHub (pinned to fa42722fef)
Solutions
- Re-request a fresh token via the temporary-URL action instead of reusing old/hand-made tokens
- Ensure the token was signed by the same app (deriveTemporaryFileAccessSecret uses plugin.app.name) with the expected audience and an object payload
- Check that the client is not mangling the token in transit (truncation, URL-encoding issues) — though typically that fails verification earlier
- If old tokens fail after an upgrade, invalidate them and re-issue; keep the token payload matching TemporaryFileAccessPayload
Example fix
// before: token created manually with a string payload
const token = jwt.sign('some-file-id', secret, { algorithm: 'HS256' });
// after: use the plugin's signing function / object payload
const token = signTemporaryFileAccessToken(plugin, { attachmentId: file.id }); Defensive patterns
Strategy: validation
Validate before calling
// Client-side sanity check before sending the token
function looksLikeTemporaryFileToken(token: string): boolean {
const parts = token.split('.');
if (parts.length !== 3) return false;
try {
const payload = JSON.parse(Buffer.from(parts[1], 'base64url').toString('utf8'));
return payload && typeof payload === 'object' && !Array.isArray(payload);
} catch {
return false;
}
}
if (!looksLikeTemporaryFileToken(token)) throw new Error('Refusing to send malformed temporary file token'); Type guard
function isTemporaryFileAccessPayload(
decoded: string | object | null,
): decoded is TemporaryFileAccessPayload {
return (
!!decoded &&
typeof decoded === 'object' &&
!Array.isArray(decoded) &&
typeof (decoded as TemporaryFileAccessPayload).attachmentId !== 'undefined'
);
} Try / catch
import { TokenExpiredError, JsonWebTokenError } from 'jsonwebtoken';
try {
const payload = verifyTemporaryFileAccessToken(plugin, token);
} catch (e) {
if (e instanceof TokenExpiredError) return respondTokenExpired();
if (e instanceof JsonWebTokenError || e.message === 'Invalid temporary file access token') {
return respondUnauthorized('Request a new temporary file URL');
}
throw e;
} Prevention
- Always obtain tokens via the plugin's signTemporaryFileAccessToken / temporary-URL action, never hand-sign them
- Ensure the verifying app name matches the signing app (secret derives from plugin.app.name)
- Re-issue tokens after plugin upgrades that change the payload schema; treat tokens as short-lived and non-persistent
- Guard the file endpoint to return 401/404 on invalid tokens instead of leaking raw errors
When it happens
Trigger: Calling the file access endpoint (getFile path) with a token that: was created with a different payload format (e.g. signed by another feature or an older plugin version using a string sub-only payload), was crafted as a string-payload JWT, or otherwise verifies cryptographically but does not decode to the expected object with the right audience.
Common situations: A token issued by a different NocoBase app name (secret derivation uses plugin.app.name, so verification would fail there rather than here); hand-rolled tokens in integrations/scripts that pass a plain string as the JWT payload; plugin upgrades changing the payload schema so old tokens no longer match the expected object shape.
Related errors
- TOKEN_RENEW_FAILED
- TEMPORARY_FILE_ACCESS_EXPIRES_IN must be between 5m and 10m
- The saved license key does not include package registry cred
- Package registry login failed with status ${response.status}
- formatSwaggerSchemaError(response, { baseUrl, token, ...cont
AI-assisted analysis of nocobase/nocobase@fa42722fef (2026-09-01).
Data as JSON: /api/errors/2064c5cf419c346e.
Report an issue: GitHub.