immich-app/immich · error · ForbiddenException
Missing required permission: ${requestedPermission}
Error message
Missing required permission: ${requestedPermission} What it means
ForbiddenException (HTTP 403) thrown by AuthService.authenticate when the request carries an API key and the route's requested permission (defaults to Permission.All when metadata.permission is unset) is not granted by the key's permissions via isGranted. The message interpolates the missing permission name to aid debugging.
Source
Thrown at server/src/services/auth.service.ts:231
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;
}
private async validate({ headers, queryParams }: Omit<ValidateRequest, 'metadata'>): Promise<AuthDto> {
const shareKey = (headers[ImmichHeader.SharedLinkKey] || queryParams[ImmichQuery.SharedLinkKey]) as string;
const shareSlug = (headers[ImmichHeader.SharedLinkSlug] || queryParams[ImmichQuery.SharedLinkSlug]) as string;
const session = (headers[ImmichHeader.UserToken] ||
headers[ImmichHeader.SessionToken] ||
queryParams[ImmichQuery.SessionKey] ||
this.getBearerToken(headers) ||
this.getCookieToken(headers)) as string;
const apiKey = (headers[ImmichHeader.ApiKey] || queryParams[ImmichQuery.ApiKey]) as string;
if (shareKey) {
return this.validateSharedLinkKey(shareKey);
}View on GitHub (pinned to 199723261c)
Solutions
- Issue a new API key whose permissions include the value named in the error message.
- If the route genuinely needs full access, use a session token instead of an API key.
- Have an admin edit the key's permissions in /api-keys settings.
- Audit isGranted outputs against the current Permission enum after upgrades.
Example fix
// before
await axios.post('/assets', data, { headers: { 'x-api-key': readOnlyKey } });
// -> 403 Missing required permission: asset.create
// after
const key = await axios.post('/api-keys', { name: 'uploader', permissions: ['asset.create'] }, { headers: { Authorization: `Bearer ${adminSession}` } });
await axios.post('/assets', data, { headers: { 'x-api-key': key.data.secret } }); Defensive patterns
Strategy: validation
Validate before calling
async function keyHasPermission(key: string, perm: string): Promise<boolean> {
const { data } = await axios.get('/api-keys', { headers: { 'x-api-key': key } });
return data.permissions?.includes(perm);
} Type guard
function keyGrants(perms: string[], required: string): boolean {
return perms.includes(required) || perms.includes('all');
} Try / catch
try {
await axios.post('/assets', data, { headers: { 'x-api-key': key } });
} catch (e) {
if (e.response?.status === 403 && /Missing required permission/.test(e.response?.data?.message || '')) {
const required = e.response.data.message.split(': ').pop();
await provisionKeyWithPermission(required);
} else throw e;
} Prevention
- Scope API keys to exactly the permissions they need; reissue when routes change.
- After upgrades, audit key permissions against the current Permission enum.
- Prefer session tokens for general/admin actions.
When it happens
Trigger: Any authenticated request made with an x-api-key whose permission set does not include the route's required Permission. Example: an API key scoped to asset.read hitting POST /assets (asset.create), or any key on a route with no explicit permission (defaults to All).
Common situations: API key created with limited scopes and reused for an admin/general call; route added or upgraded to require a permission the key lacks; client hard-codes a stale API key after permissions were tightened.
Related errors
- Forbidden
- Cannot grant permissions you do not have
- Not authenticated with an API Key
- User already has a PIN code
- User does not have a PIN code
AI-assisted analysis of immich-app/immich@199723261c (2026-08-12).
Data as JSON: /api/errors/5d9ece26c6055f27.
Report an issue: GitHub.