immich-app/immich · error · ForbiddenException
Forbidden
Error message
Forbidden
What it means
ForbiddenException (HTTP 403) thrown by AuthService.authenticate when metadata.adminRoute is true and the resolved user is not an admin. The auth guard attaches adminRoute metadata to admin-only endpoints; non-admins are rejected before the handler runs. A warn log records the denied URI for auditing.
Source
Thrown at server/src/services/auth.service.ts:218
const admin = await this.createUser({
isAdmin: true,
email: dto.email,
name: dto.name,
password: dto.password,
storageLabel: 'admin',
});
return mapUserAdmin(admin);
}
async authenticate({ headers, queryParams, metadata }: ValidateRequest): Promise<AuthDto> {
const authDto = await this.validate({ headers, queryParams });
const { adminRoute, sharedLinkRoute, uri } = metadata;
const requestedPermission = metadata.permission ?? Permission.All;
if (!authDto.user.isAdmin && adminRoute) {
this.logger.warn(`Denied access to admin only route: ${uri}`);
throw new ForbiddenException('Forbidden');
}
if (authDto.sharedLink && !sharedLinkRoute) {
this.logger.warn(`Denied access to non-shared route: ${uri}`);
throw new ForbiddenException('Forbidden');
}
if (
authDto.apiKey &&
requestedPermission !== false &&
!isGranted({ requested: [requestedPermission], current: authDto.apiKey.permissions })
) {
throw new ForbiddenException(`Missing required permission: ${requestedPermission}`);
}
return authDto;
}
View on GitHub (pinned to 199723261c)
Solutions
- Use an admin user's session token/API key for admin endpoints.
- If the user was recently promoted, log out and back in to refresh the cached isAdmin flag.
- Gate the admin UI behind an isAdmin check before issuing the request.
- Check server logs for the warn line 'Denied access to admin only route' to identify the offending token.
Example fix
// before
const client = axios.create({ headers: { Authorization: `Bearer ${userToken}` } });
await client.delete('/admin/users/123');
// after
const me = await api.userApi.getMyUserInfo();
if (!me.isAdmin) throw new Error('admin required');
const client = axios.create({ headers: { Authorization: `Bearer ${adminToken}` } });
await client.delete('/admin/users/123'); Defensive patterns
Strategy: validation
Validate before calling
async function isAdmin(token: string): Promise<boolean> {
const { data } = await axios.get('/users/me', { headers: { Authorization: `Bearer ${token}` } });
return data.isAdmin === true;
} Type guard
function isAdminUser(user: { isAdmin?: boolean }): user is { isAdmin: true } {
return user.isAdmin === true;
} Try / catch
try {
await axios.delete(`/admin/users/${id}`, { headers: auth() });
} catch (e) {
if (e.response?.status === 403) {
showInsufficientPrivileges();
} else throw e;
} Prevention
- Gate admin UI on a fresh /users/me.isAdmin check.
- Re-login after role changes to refresh cached claims.
- Log 403s and surface them as permission errors, not generic failures.
When it happens
Trigger: Any request to an admin-only route (e.g. DELETE /admin/users/:id, POST /system/preferences) carrying a valid non-admin session/API-key/shared-link token. The token authenticates successfully but authorization fails at the adminRoute check.
Common situations: Normal user token reused against admin endpoints after a role demotion; client uses the wrong account; shared link or API key with insufficient privileges hitting admin endpoints; JWT not refreshed after an admin role change.
Understand the failure class
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- Missing required permission: ${requestedPermission}
- Not in maintenance mode
- User already has a PIN code
- User does not have a PIN code
- Wrong PIN code
AI-assisted analysis of immich-app/immich@199723261c (2026-08-12).
Data as JSON: /api/errors/a7d2ca0012586bf1.
Report an issue: GitHub.