{"record":{"id":"a29f5b50ea7f8431","repo":"RocketChat/Rocket.Chat","slug":"the-server-detected-an-unauthenticated-access-to-a","errorCode":null,"errorMessage":"The server detected an unauthenticated access to an user avatar. This type of request will soon be blocked by default.","messagePattern":"The server detected an unauthenticated access to an user avatar\\. This type of request will soon be blocked by default\\.","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"apps/meteor/server/routes/avatar/utils.ts","lineNumber":110,"sourceCode":"async function isUserAuthenticated({ headers, query }: Pick<IIncomingMessage, 'headers' | 'query'>) {\n\tlet { rc_uid, rc_token } = query;\n\n\tif (!rc_uid && headers.cookie) {\n\t\trc_uid = cookie.get('rc_uid', headers.cookie);\n\t\trc_token = cookie.get('rc_token', headers.cookie);\n\t}\n\n\tif (rc_uid == null || rc_token == null) {\n\t\treturn false;\n\t}\n\n\tconst userFound = await Users.findOneByIdAndLoginToken(rc_uid, hashLoginToken(rc_token), { projection: { _id: 1 } }); // TODO memoize find\n\n\treturn !!userFound;\n}\n\nconst warnUnauthenticatedAccess = throttle(() => {\n\tconsole.warn('The server detected an unauthenticated access to an user avatar. This type of request will soon be blocked by default.');\n}, 60000 * 30); // 30 minutes\n\nexport async function userCanAccessAvatar({ headers = {}, query = {} }: IIncomingMessage) {\n\tif (!settings.get('Accounts_AvatarBlockUnauthenticatedAccess')) {\n\t\treturn true;\n\t}\n\n\tconst isAuthenticated = await isUserAuthenticated({ headers, query });\n\tif (!isAuthenticated) {\n\t\twarnUnauthenticatedAccess();\n\t}\n\n\treturn isAuthenticated;\n}\n\nconst getFirstLetter = (name: string) => {\n\tconst sanitizedName = sanitizeHtml(name);\n\treturn sanitizedName.substring(0, 1).toUpperCase();","sourceCodeStart":92,"sourceCodeEnd":128,"githubUrl":"https://github.com/RocketChat/Rocket.Chat/blob/b2c16d5842cbe6b69b59bdf6fc5e5f1afcd1f0b0/apps/meteor/server/routes/avatar/utils.ts#L92-L128","documentation":"Logged (throttled to once per 30 minutes) by userCanAccessAvatar in apps/meteor/server/routes/avatar/utils.ts. When the setting Accounts_AvatarBlockUnauthenticatedAccess is enabled, every avatar request is authenticated via rc_uid + rc_token (headers or query) checked against Users.findOneByIdAndLoginToken(hashLoginToken(rc_token)). If the credentials are absent or invalid, this warning fires — it announces that unauthenticated avatar access, historically allowed, will be blocked by default in a future version.","triggerScenarios":"A request to /avatar/<username> (or /avatar/<username>.jpg) arrives with Accounts_AvatarBlockUnauthenticatedAccess = true, and the request carries no rc_uid/rc_token pair, or the token does not hash-match a stored login token for that user. Frequent producers: <img src=\"/avatar/...\"> embedded in emails, external websites, or chat previews; proxies that strip cookies; scrapers and monitoring tools hitting avatar URLs; logged-out pages hotlinking avatars.","commonSituations":"Administrators enabling Accounts_AvatarBlockUnauthenticatedAccess ahead of the default flip; webhook/notification emails that inline avatar URLs without auth tokens; migrating public workspaces to private ones; CDN caching of avatar URLs that were fetched anonymously. Once the setting blocks, those images return 401/403 instead of rendering.","solutions":["Identify the referrers hitting /avatar/* without credentials (access logs) — usually email templates, external sites, or integrations.","Serve avatars to logged-in clients only: use the authenticated browser session (cookies) or append valid rc_uid + rc_token query params for machine access.","For emails/external embeds, stop inlining the server avatar URL; use the recipient-side rendered avatar or a public CDN asset instead.","If anonymous access is an intentional product requirement for your deployment, keep Accounts_AvatarBlockUnauthenticatedAccess = false and document the future behavior change."],"exampleFix":"// before: anonymous embed that triggers the warning once the setting is on\n<img src=\"https://chat.example.com/avatar/john.doe\" />\n\n// after: authenticated request with token pair\nconst url = `https://chat.example.com/avatar/john.doe?rc_uid=${userId}&rc_token=${authToken}`;\n// (fetch token via REST login: POST /api/v1/login)","handlingStrategy":"validation","validationCode":"// Client-side: verify credentials exist before requesting a blocked avatar\nconst canFetchAvatar = (userId: string | null, token: string | null): boolean =>\n  userId != null && token != null;\n\nconst url = canFetchAvatar(Meteor.userId(), Meteor._localStorage.getItem('Meteor.loginToken'))\n  ? `/avatar/${username}?rc_uid=${Meteor.userId()}&rc_token=${token}`\n  : null; // fall back to a local placeholder instead of an anonymous request","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Never embed raw /avatar/<user> URLs in emails or external pages; those requests are anonymous by construction.","Pass rc_uid and rc_token (or rely on the authenticated session cookie) for every avatar request from scripts and integrations.","Audit access logs for anonymous /avatar/* traffic BEFORE enabling Accounts_AvatarBlockUnauthenticatedAccess so nothing breaks when it blocks."],"tags":["avatar","authentication","http-routes","access-control","deprecation"],"backgroundTag":"unauthenticated-request-blocked","analyzedSha":"b2c16d5842cbe6b69b59bdf6fc5e5f1afcd1f0b0","analyzedAt":"2026-08-18T15:26:39.429Z","contentChangedAt":"2026-08-18T15:26:39.429Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}