immich-app/immich · error · UnauthorizedException
Invalid share key
Error message
Invalid share key
What it means
Shared-link (public sharing) authentication decodes the provided key as hex (when 100 chars) or base64url, looks it up in the shared_link table, and validates it exists and has not expired. If the repository returns nothing or the link is invalid/expired, an UnauthorizedException is thrown.
Solutions
- Regenerate the share link from the owner account and use the fresh key
- Verify the full key was copied (no truncation, correct URL-encoding)
- Have the owner extend or remove expiresAt on the share link
- Check the server database/backup to confirm the key still exists
Defensive patterns
Strategy: try-catch
Type guard
const isPlausibleKey = (k: string) => k.length === 100 || /^[A-Za-z0-9_-]+$/.test(k);
Try / catch
catch (e) { if (e.status === 401 && e.message === 'Invalid share key') { /* regenerate link */ } } Prevention
- Copy full URLs without truncation
- Track link expiry and regenerate proactively
- Re-create links after migrations
When it happens
Trigger: GET with ?key=... where the key is malformed, deleted, revoked, or the share link expired (expiresAt in the past).
Common situations: Copying a truncated URL; share links revoked by the owner after password/key rotation; stale bookmarks after a migration that regenerated keys; expiry passing between generating and using the link.
Related errors
AI-assisted analysis of immich-app/immich@f48d4b3321 (2026-09-15).
Data as JSON: /api/errors/c22811fb167fdb94.
Report an issue: GitHub.
Appendix: source
Thrown at server/src/services/auth.service.ts:504
}
private getCookieOauthState(headers: IncomingHttpHeaders): string | null {
const cookies = parse(headers.cookie || '');
return cookies[ImmichCookie.OAuthState] || null;
}
private getCookieCodeVerifier(headers: IncomingHttpHeaders): string | null {
const cookies = parse(headers.cookie || '');
return cookies[ImmichCookie.OAuthCodeVerifier] || null;
}
async validateSharedLinkKey(key: string | string[]): Promise<AuthDto> {
key = Array.isArray(key) ? key[0] : key;
const bytes = Buffer.from(key, key.length === 100 ? 'hex' : 'base64url');
const sharedLink = await this.sharedLinkRepository.getByKey(bytes);
if (!this.isValidSharedLink(sharedLink)) {
throw new UnauthorizedException('Invalid share key');
}
return { user: sharedLink.user, sharedLink };
}
async validateSharedLinkSlug(slug: string | string[]): Promise<AuthDto> {
slug = Array.isArray(slug) ? slug[0] : slug;
const sharedLink = await this.sharedLinkRepository.getBySlug(slug);
if (!this.isValidSharedLink(sharedLink)) {
throw new UnauthorizedException('Invalid share slug');
}
return { user: sharedLink.user, sharedLink };
}
private isValidSharedLink(
sharedLink?: AuthSharedLink & { user: AuthUser | null },View on GitHub (pinned to f48d4b3321)