immich-app/immich · error · UnauthorizedException
Invalid share slug
Error message
Invalid share slug
What it means
Thrown by AuthService.validateSharedLinkSlug when a share link cannot be resolved or is no longer usable. The slug is looked up via sharedLinkRepository.getBySlug, then isValidSharedLink asserts the link exists, has a non-null user, and has not passed its expiresAt. If any of those fail the request is treated as unauthenticated.
Source
Thrown at server/src/services/auth.service.ts:511
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 },
): sharedLink is AuthSharedLink & { user: AuthUser } {
return !!sharedLink?.user && (!sharedLink.expiresAt || new Date(sharedLink.expiresAt) > new Date());
}
private async validateApiKey(key: string): Promise<AuthDto> {
const hashed = this.cryptoRepository.hashSha256(key);
const apiKey = await this.apiKeyRepository.getKey(hashed);
if (apiKey?.user) {
return {
user: apiKey.user,
apiKey,View on GitHub (pinned to 199723261c)
Solutions
- Verify the exact slug value against what is stored/generated by the server (no extra slashes, query params, or whitespace).
- Check whether the shared link has expired (expiresAt) and regenerate it if needed.
- Confirm the owning user still exists; recreate the share from a valid account if the user was deleted.
- Catch UnauthorizedException at the controller edge and surface a 401 with a 'link invalid or expired' message to the client.
Example fix
// before: blindly trust an unvalidated slug from a URL
const auth = await authService.validateSharedLinkSlug(req.query.slug);
// after: normalize + presence-check, then handle the auth failure
const raw = Array.isArray(req.query.slug) ? req.query.slug[0] : req.query.slug;
if (!raw || typeof raw !== 'string' || raw.trim().length === 0) {
throw new UnauthorizedException('Missing share slug');
}
let auth: AuthDto;
try {
auth = await authService.validateSharedLinkSlug(raw.trim());
} catch (e) {
if (e instanceof UnauthorizedException) {
throw new UnauthorizedException('Share link is invalid or expired');
}
throw e;
} Defensive patterns
Strategy: try-catch
Validate before calling
const slug = Array.isArray(req.query.slug) ? req.query.slug[0] : req.query.slug;
if (!slug || typeof slug !== 'string' || slug.trim().length === 0) {
throw new UnauthorizedException('A share slug is required');
} Type guard
function isShareSlug(v: unknown): v is string {
return typeof v === 'string' && v.trim().length > 0 && !/[/\\]/.test(v);
} Try / catch
try {
const auth = await authService.validateSharedLinkSlug(slug);
} catch (e) {
if (e instanceof UnauthorizedException) {
// 401 to client: link unknown, expired, or orphaned
throw new UnauthorizedException('Share link is invalid or expired');
}
throw e;
} Prevention
- Trim and length-check the slug before sending it to the auth service.
- Regenerate share links with a sensible expiresAt and communicate expiry to consumers.
- When deleting a user, invalidate that user's shared links to avoid orphaned slugs.
When it happens
Trigger: A request authenticated by a share-link slug (e.g. a public-album / shared-asset route that accepts ?slug= or a path segment) where the slug is unknown, was deleted, belongs to a link whose user was removed, or whose expiresAt is in the past.
Common situations: Stale bookmarks to a shared album whose link was regenerated or expired; copy-paste of a slug with a trailing space or missing segment; the owning user account was deleted leaving sharedLink.user null; clock skew where a link intended to be live is treated as expired.
Related errors
- Password login has been disabled
- Incorrect email or password
- Error backchannel logout: token validation failed
- Wrong password
- Unauthorized
AI-assisted analysis of immich-app/immich@199723261c (2026-08-12).
Data as JSON: /api/errors/3334301cb923d4df.
Report an issue: GitHub.