diff --git a/open-api/immich-openapi-specs.json b/open-api/immich-openapi-specs.json
index 993705f97a..eea90fb1c9 100644
--- a/open-api/immich-openapi-specs.json
+++ b/open-api/immich-openapi-specs.json
@@ -5806,15 +5806,6 @@
           }
         },
         "security": [
-          {
-            "bearer": []
-          },
-          {
-            "cookie": []
-          },
-          {
-            "api_key": []
-          },
           {
             "bearer": []
           },
@@ -5942,15 +5933,6 @@
           }
         },
         "security": [
-          {
-            "bearer": []
-          },
-          {
-            "cookie": []
-          },
-          {
-            "api_key": []
-          },
           {
             "bearer": []
           },
diff --git a/server/src/controllers/activity.controller.ts b/server/src/controllers/activity.controller.ts
index a65b284ca1..9a5fc41885 100644
--- a/server/src/controllers/activity.controller.ts
+++ b/server/src/controllers/activity.controller.ts
@@ -15,21 +15,23 @@ import { UUIDParamDto } from 'src/validation';
 
 @ApiTags('Activity')
 @Controller('activity')
-@Authenticated()
 export class ActivityController {
   constructor(private service: ActivityService) {}
 
   @Get()
+  @Authenticated()
   getActivities(@Auth() auth: AuthDto, @Query() dto: ActivitySearchDto): Promise<ActivityResponseDto[]> {
     return this.service.getAll(auth, dto);
   }
 
   @Get('statistics')
+  @Authenticated()
   getActivityStatistics(@Auth() auth: AuthDto, @Query() dto: ActivityDto): Promise<ActivityStatisticsResponseDto> {
     return this.service.getStatistics(auth, dto);
   }
 
   @Post()
+  @Authenticated()
   async createActivity(
     @Auth() auth: AuthDto,
     @Body() dto: ActivityCreateDto,
@@ -44,6 +46,7 @@ export class ActivityController {
 
   @Delete(':id')
   @HttpCode(HttpStatus.NO_CONTENT)
+  @Authenticated()
   deleteActivity(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<void> {
     return this.service.delete(auth, id);
   }
diff --git a/server/src/controllers/album.controller.ts b/server/src/controllers/album.controller.ts
index 0e8d954ab8..c38f733b42 100644
--- a/server/src/controllers/album.controller.ts
+++ b/server/src/controllers/album.controller.ts
@@ -12,32 +12,34 @@ import {
 } from 'src/dtos/album.dto';
 import { BulkIdResponseDto, BulkIdsDto } from 'src/dtos/asset-ids.response.dto';
 import { AuthDto } from 'src/dtos/auth.dto';
-import { Auth, Authenticated, SharedLinkRoute } from 'src/middleware/auth.guard';
+import { Auth, Authenticated } from 'src/middleware/auth.guard';
 import { AlbumService } from 'src/services/album.service';
 import { ParseMeUUIDPipe, UUIDParamDto } from 'src/validation';
 
 @ApiTags('Album')
 @Controller('album')
-@Authenticated()
 export class AlbumController {
   constructor(private service: AlbumService) {}
 
   @Get('count')
+  @Authenticated()
   getAlbumCount(@Auth() auth: AuthDto): Promise<AlbumCountResponseDto> {
     return this.service.getCount(auth);
   }
 
   @Get()
+  @Authenticated()
   getAllAlbums(@Auth() auth: AuthDto, @Query() query: GetAlbumsDto): Promise<AlbumResponseDto[]> {
     return this.service.getAll(auth, query);
   }
 
   @Post()
+  @Authenticated()
   createAlbum(@Auth() auth: AuthDto, @Body() dto: CreateAlbumDto): Promise<AlbumResponseDto> {
     return this.service.create(auth, dto);
   }
 
-  @SharedLinkRoute()
+  @Authenticated({ sharedLink: true })
   @Get(':id')
   getAlbumInfo(
     @Auth() auth: AuthDto,
@@ -48,6 +50,7 @@ export class AlbumController {
   }
 
   @Patch(':id')
+  @Authenticated()
   updateAlbumInfo(
     @Auth() auth: AuthDto,
     @Param() { id }: UUIDParamDto,
@@ -57,12 +60,13 @@ export class AlbumController {
   }
 
   @Delete(':id')
+  @Authenticated()
   deleteAlbum(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto) {
     return this.service.delete(auth, id);
   }
 
-  @SharedLinkRoute()
   @Put(':id/assets')
+  @Authenticated({ sharedLink: true })
   addAssetsToAlbum(
     @Auth() auth: AuthDto,
     @Param() { id }: UUIDParamDto,
@@ -72,6 +76,7 @@ export class AlbumController {
   }
 
   @Delete(':id/assets')
+  @Authenticated()
   removeAssetFromAlbum(
     @Auth() auth: AuthDto,
     @Body() dto: BulkIdsDto,
@@ -81,6 +86,7 @@ export class AlbumController {
   }
 
   @Put(':id/users')
+  @Authenticated()
   addUsersToAlbum(
     @Auth() auth: AuthDto,
     @Param() { id }: UUIDParamDto,
@@ -90,6 +96,7 @@ export class AlbumController {
   }
 
   @Put(':id/user/:userId')
+  @Authenticated()
   updateAlbumUser(
     @Auth() auth: AuthDto,
     @Param() { id }: UUIDParamDto,
@@ -100,6 +107,7 @@ export class AlbumController {
   }
 
   @Delete(':id/user/:userId')
+  @Authenticated()
   removeUserFromAlbum(
     @Auth() auth: AuthDto,
     @Param() { id }: UUIDParamDto,
diff --git a/server/src/controllers/api-key.controller.ts b/server/src/controllers/api-key.controller.ts
index 564b903874..e0b07ede50 100644
--- a/server/src/controllers/api-key.controller.ts
+++ b/server/src/controllers/api-key.controller.ts
@@ -8,26 +8,29 @@ import { UUIDParamDto } from 'src/validation';
 
 @ApiTags('API Key')
 @Controller('api-key')
-@Authenticated()
 export class APIKeyController {
   constructor(private service: APIKeyService) {}
 
   @Post()
+  @Authenticated()
   createApiKey(@Auth() auth: AuthDto, @Body() dto: APIKeyCreateDto): Promise<APIKeyCreateResponseDto> {
     return this.service.create(auth, dto);
   }
 
   @Get()
+  @Authenticated()
   getApiKeys(@Auth() auth: AuthDto): Promise<APIKeyResponseDto[]> {
     return this.service.getAll(auth);
   }
 
   @Get(':id')
+  @Authenticated()
   getApiKey(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<APIKeyResponseDto> {
     return this.service.getById(auth, id);
   }
 
   @Put(':id')
+  @Authenticated()
   updateApiKey(
     @Auth() auth: AuthDto,
     @Param() { id }: UUIDParamDto,
@@ -37,6 +40,7 @@ export class APIKeyController {
   }
 
   @Delete(':id')
+  @Authenticated()
   deleteApiKey(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<void> {
     return this.service.delete(auth, id);
   }
diff --git a/server/src/controllers/app.controller.ts b/server/src/controllers/app.controller.ts
index 472d0da3f7..3fe9b49368 100644
--- a/server/src/controllers/app.controller.ts
+++ b/server/src/controllers/app.controller.ts
@@ -1,6 +1,5 @@
 import { Controller, Get, Header } from '@nestjs/common';
 import { ApiExcludeEndpoint } from '@nestjs/swagger';
-import { PublicRoute } from 'src/middleware/auth.guard';
 import { SystemConfigService } from 'src/services/system-config.service';
 
 @Controller()
@@ -18,7 +17,6 @@ export class AppController {
   }
 
   @ApiExcludeEndpoint()
-  @PublicRoute()
   @Get('custom.css')
   @Header('Content-Type', 'text/css')
   getCustomCss() {
diff --git a/server/src/controllers/asset-v1.controller.ts b/server/src/controllers/asset-v1.controller.ts
index 908569b58f..2f093e1d1f 100644
--- a/server/src/controllers/asset-v1.controller.ts
+++ b/server/src/controllers/asset-v1.controller.ts
@@ -31,7 +31,7 @@ import {
 } from 'src/dtos/asset-v1.dto';
 import { AuthDto, ImmichHeader } from 'src/dtos/auth.dto';
 import { AssetUploadInterceptor } from 'src/middleware/asset-upload.interceptor';
-import { Auth, Authenticated, FileResponse, SharedLinkRoute } from 'src/middleware/auth.guard';
+import { Auth, Authenticated, FileResponse } from 'src/middleware/auth.guard';
 import { FileUploadInterceptor, ImmichFile, Route, mapToUploadFile } from 'src/middleware/file-upload.interceptor';
 import { AssetServiceV1 } from 'src/services/asset-v1.service';
 import { sendFile } from 'src/utils/file';
@@ -45,11 +45,9 @@ interface UploadFiles {
 
 @ApiTags('Asset')
 @Controller(Route.ASSET)
-@Authenticated()
 export class AssetControllerV1 {
   constructor(private service: AssetServiceV1) {}
 
-  @SharedLinkRoute()
   @Post('upload')
   @UseInterceptors(AssetUploadInterceptor, FileUploadInterceptor)
   @ApiConsumes('multipart/form-data')
@@ -58,10 +56,8 @@ export class AssetControllerV1 {
     description: 'sha1 checksum that can be used for duplicate detection before the file is uploaded',
     required: false,
   })
-  @ApiBody({
-    description: 'Asset Upload Information',
-    type: CreateAssetDto,
-  })
+  @ApiBody({ description: 'Asset Upload Information', type: CreateAssetDto })
+  @Authenticated({ sharedLink: true })
   async uploadFile(
     @Auth() auth: AuthDto,
     @UploadedFiles(new ParseFilePipe({ validators: [new FileNotEmptyValidator(['assetData'])] })) files: UploadFiles,
@@ -89,9 +85,9 @@ export class AssetControllerV1 {
     return responseDto;
   }
 
-  @SharedLinkRoute()
   @Get('/file/:id')
   @FileResponse()
+  @Authenticated({ sharedLink: true })
   async serveFile(
     @Res() res: Response,
     @Next() next: NextFunction,
@@ -102,9 +98,9 @@ export class AssetControllerV1 {
     await sendFile(res, next, () => this.service.serveFile(auth, id, dto));
   }
 
-  @SharedLinkRoute()
   @Get('/thumbnail/:id')
   @FileResponse()
+  @Authenticated({ sharedLink: true })
   async getAssetThumbnail(
     @Res() res: Response,
     @Next() next: NextFunction,
@@ -125,6 +121,7 @@ export class AssetControllerV1 {
     required: false,
     schema: { type: 'string' },
   })
+  @Authenticated()
   getAllAssets(@Auth() auth: AuthDto, @Query() dto: AssetSearchDto): Promise<AssetResponseDto[]> {
     return this.service.getAllAssets(auth, dto);
   }
@@ -134,6 +131,7 @@ export class AssetControllerV1 {
    */
   @Post('/exist')
   @HttpCode(HttpStatus.OK)
+  @Authenticated()
   checkExistingAssets(
     @Auth() auth: AuthDto,
     @Body() dto: CheckExistingAssetsDto,
@@ -146,6 +144,7 @@ export class AssetControllerV1 {
    */
   @Post('/bulk-upload-check')
   @HttpCode(HttpStatus.OK)
+  @Authenticated()
   checkBulkUpload(
     @Auth() auth: AuthDto,
     @Body() dto: AssetBulkUploadCheckDto,
diff --git a/server/src/controllers/asset.controller.ts b/server/src/controllers/asset.controller.ts
index 9db27998d2..f2d076e17b 100644
--- a/server/src/controllers/asset.controller.ts
+++ b/server/src/controllers/asset.controller.ts
@@ -14,28 +14,30 @@ import {
 import { AuthDto } from 'src/dtos/auth.dto';
 import { MapMarkerDto, MapMarkerResponseDto, MemoryLaneDto } from 'src/dtos/search.dto';
 import { UpdateStackParentDto } from 'src/dtos/stack.dto';
-import { Auth, Authenticated, SharedLinkRoute } from 'src/middleware/auth.guard';
+import { Auth, Authenticated } from 'src/middleware/auth.guard';
 import { Route } from 'src/middleware/file-upload.interceptor';
 import { AssetService } from 'src/services/asset.service';
 import { UUIDParamDto } from 'src/validation';
 
 @ApiTags('Asset')
 @Controller(Route.ASSET)
-@Authenticated()
 export class AssetController {
   constructor(private service: AssetService) {}
 
   @Get('map-marker')
+  @Authenticated()
   getMapMarkers(@Auth() auth: AuthDto, @Query() options: MapMarkerDto): Promise<MapMarkerResponseDto[]> {
     return this.service.getMapMarkers(auth, options);
   }
 
   @Get('memory-lane')
+  @Authenticated()
   getMemoryLane(@Auth() auth: AuthDto, @Query() dto: MemoryLaneDto): Promise<MemoryLaneResponseDto[]> {
     return this.service.getMemoryLane(auth, dto);
   }
 
   @Get('random')
+  @Authenticated()
   getRandom(@Auth() auth: AuthDto, @Query() dto: RandomAssetsDto): Promise<AssetResponseDto[]> {
     return this.service.getRandom(auth, dto.count ?? 1);
   }
@@ -44,46 +46,53 @@ export class AssetController {
    * Get all asset of a device that are in the database, ID only.
    */
   @Get('/device/:deviceId')
+  @Authenticated()
   getAllUserAssetsByDeviceId(@Auth() auth: AuthDto, @Param() { deviceId }: DeviceIdDto) {
     return this.service.getUserAssetsByDeviceId(auth, deviceId);
   }
 
   @Get('statistics')
+  @Authenticated()
   getAssetStatistics(@Auth() auth: AuthDto, @Query() dto: AssetStatsDto): Promise<AssetStatsResponseDto> {
     return this.service.getStatistics(auth, dto);
   }
 
   @Post('jobs')
   @HttpCode(HttpStatus.NO_CONTENT)
+  @Authenticated()
   runAssetJobs(@Auth() auth: AuthDto, @Body() dto: AssetJobsDto): Promise<void> {
     return this.service.run(auth, dto);
   }
 
   @Put()
   @HttpCode(HttpStatus.NO_CONTENT)
+  @Authenticated()
   updateAssets(@Auth() auth: AuthDto, @Body() dto: AssetBulkUpdateDto): Promise<void> {
     return this.service.updateAll(auth, dto);
   }
 
   @Delete()
   @HttpCode(HttpStatus.NO_CONTENT)
+  @Authenticated()
   deleteAssets(@Auth() auth: AuthDto, @Body() dto: AssetBulkDeleteDto): Promise<void> {
     return this.service.deleteAll(auth, dto);
   }
 
   @Put('stack/parent')
   @HttpCode(HttpStatus.OK)
+  @Authenticated()
   updateStackParent(@Auth() auth: AuthDto, @Body() dto: UpdateStackParentDto): Promise<void> {
     return this.service.updateStackParent(auth, dto);
   }
 
-  @SharedLinkRoute()
   @Get(':id')
+  @Authenticated({ sharedLink: true })
   getAssetInfo(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<AssetResponseDto> {
     return this.service.get(auth, id) as Promise<AssetResponseDto>;
   }
 
   @Put(':id')
+  @Authenticated()
   updateAsset(
     @Auth() auth: AuthDto,
     @Param() { id }: UUIDParamDto,
diff --git a/server/src/controllers/audit.controller.ts b/server/src/controllers/audit.controller.ts
index 8eea6a6e3e..856a1cc755 100644
--- a/server/src/controllers/audit.controller.ts
+++ b/server/src/controllers/audit.controller.ts
@@ -7,11 +7,11 @@ import { AuditService } from 'src/services/audit.service';
 
 @ApiTags('Audit')
 @Controller('audit')
-@Authenticated()
 export class AuditController {
   constructor(private service: AuditService) {}
 
   @Get('deletes')
+  @Authenticated()
   getAuditDeletes(@Auth() auth: AuthDto, @Query() dto: AuditDeletesDto): Promise<AuditDeletesResponseDto> {
     return this.service.getDeletes(auth, dto);
   }
diff --git a/server/src/controllers/auth.controller.ts b/server/src/controllers/auth.controller.ts
index a4c7494f2b..40fdf90916 100644
--- a/server/src/controllers/auth.controller.ts
+++ b/server/src/controllers/auth.controller.ts
@@ -13,17 +13,15 @@ import {
   ValidateAccessTokenResponseDto,
 } from 'src/dtos/auth.dto';
 import { UserResponseDto, mapUser } from 'src/dtos/user.dto';
-import { Auth, Authenticated, GetLoginDetails, PublicRoute } from 'src/middleware/auth.guard';
+import { Auth, Authenticated, GetLoginDetails } from 'src/middleware/auth.guard';
 import { AuthService, LoginDetails } from 'src/services/auth.service';
 import { respondWithCookie, respondWithoutCookie } from 'src/utils/response';
 
 @ApiTags('Authentication')
 @Controller('auth')
-@Authenticated()
 export class AuthController {
   constructor(private service: AuthService) {}
 
-  @PublicRoute()
   @Post('login')
   async login(
     @Body() loginCredential: LoginCredentialDto,
@@ -41,7 +39,6 @@ export class AuthController {
     });
   }
 
-  @PublicRoute()
   @Post('admin-sign-up')
   signUpAdmin(@Body() dto: SignUpDto): Promise<UserResponseDto> {
     return this.service.adminSignUp(dto);
@@ -49,18 +46,21 @@ export class AuthController {
 
   @Post('validateToken')
   @HttpCode(HttpStatus.OK)
+  @Authenticated()
   validateAccessToken(): ValidateAccessTokenResponseDto {
     return { authStatus: true };
   }
 
   @Post('change-password')
   @HttpCode(HttpStatus.OK)
+  @Authenticated()
   changePassword(@Auth() auth: AuthDto, @Body() dto: ChangePasswordDto): Promise<UserResponseDto> {
     return this.service.changePassword(auth, dto).then(mapUser);
   }
 
   @Post('logout')
   @HttpCode(HttpStatus.OK)
+  @Authenticated()
   async logout(
     @Req() request: Request,
     @Res({ passthrough: true }) res: Response,
diff --git a/server/src/controllers/download.controller.ts b/server/src/controllers/download.controller.ts
index 4e4bf09d11..c9f26864af 100644
--- a/server/src/controllers/download.controller.ts
+++ b/server/src/controllers/download.controller.ts
@@ -4,35 +4,34 @@ import { NextFunction, Response } from 'express';
 import { AssetIdsDto } from 'src/dtos/asset.dto';
 import { AuthDto } from 'src/dtos/auth.dto';
 import { DownloadInfoDto, DownloadResponseDto } from 'src/dtos/download.dto';
-import { Auth, Authenticated, FileResponse, SharedLinkRoute } from 'src/middleware/auth.guard';
+import { Auth, Authenticated, FileResponse } from 'src/middleware/auth.guard';
 import { DownloadService } from 'src/services/download.service';
 import { asStreamableFile, sendFile } from 'src/utils/file';
 import { UUIDParamDto } from 'src/validation';
 
 @ApiTags('Download')
 @Controller('download')
-@Authenticated()
 export class DownloadController {
   constructor(private service: DownloadService) {}
 
-  @SharedLinkRoute()
   @Post('info')
+  @Authenticated({ sharedLink: true })
   getDownloadInfo(@Auth() auth: AuthDto, @Body() dto: DownloadInfoDto): Promise<DownloadResponseDto> {
     return this.service.getDownloadInfo(auth, dto);
   }
 
-  @SharedLinkRoute()
   @Post('archive')
   @HttpCode(HttpStatus.OK)
   @FileResponse()
+  @Authenticated({ sharedLink: true })
   downloadArchive(@Auth() auth: AuthDto, @Body() dto: AssetIdsDto): Promise<StreamableFile> {
     return this.service.downloadArchive(auth, dto).then(asStreamableFile);
   }
 
-  @SharedLinkRoute()
   @Post('asset/:id')
   @HttpCode(HttpStatus.OK)
   @FileResponse()
+  @Authenticated({ sharedLink: true })
   async downloadFile(
     @Res() res: Response,
     @Next() next: NextFunction,
diff --git a/server/src/controllers/face.controller.ts b/server/src/controllers/face.controller.ts
index a3f33fb867..5b45432944 100644
--- a/server/src/controllers/face.controller.ts
+++ b/server/src/controllers/face.controller.ts
@@ -8,16 +8,17 @@ import { UUIDParamDto } from 'src/validation';
 
 @ApiTags('Face')
 @Controller('face')
-@Authenticated()
 export class FaceController {
   constructor(private service: PersonService) {}
 
   @Get()
+  @Authenticated()
   getFaces(@Auth() auth: AuthDto, @Query() dto: FaceDto): Promise<AssetFaceResponseDto[]> {
     return this.service.getFacesById(auth, dto);
   }
 
   @Put(':id')
+  @Authenticated()
   reassignFacesById(
     @Auth() auth: AuthDto,
     @Param() { id }: UUIDParamDto,
diff --git a/server/src/controllers/file-report.controller.ts b/server/src/controllers/file-report.controller.ts
index 6bdf726073..1f9ebe52dd 100644
--- a/server/src/controllers/file-report.controller.ts
+++ b/server/src/controllers/file-report.controller.ts
@@ -1,29 +1,28 @@
 import { Body, Controller, Get, Post } from '@nestjs/common';
 import { ApiTags } from '@nestjs/swagger';
 import { FileChecksumDto, FileChecksumResponseDto, FileReportDto, FileReportFixDto } from 'src/dtos/audit.dto';
-import { AdminRoute, Authenticated } from 'src/middleware/auth.guard';
+import { Authenticated } from 'src/middleware/auth.guard';
 import { AuditService } from 'src/services/audit.service';
 
 @ApiTags('File Report')
 @Controller('report')
-@Authenticated()
 export class ReportController {
   constructor(private service: AuditService) {}
 
-  @AdminRoute()
   @Get()
+  @Authenticated({ admin: true })
   getAuditFiles(): Promise<FileReportDto> {
     return this.service.getFileReport();
   }
 
-  @AdminRoute()
-  @Post('/checksum')
+  @Post('checksum')
+  @Authenticated({ admin: true })
   getFileChecksums(@Body() dto: FileChecksumDto): Promise<FileChecksumResponseDto[]> {
     return this.service.getChecksums(dto);
   }
 
-  @AdminRoute()
-  @Post('/fix')
+  @Post('fix')
+  @Authenticated({ admin: true })
   fixAuditFiles(@Body() dto: FileReportFixDto): Promise<void> {
     return this.service.fixItems(dto.items);
   }
diff --git a/server/src/controllers/job.controller.ts b/server/src/controllers/job.controller.ts
index d6bd45b1e8..2d23263221 100644
--- a/server/src/controllers/job.controller.ts
+++ b/server/src/controllers/job.controller.ts
@@ -6,16 +6,17 @@ import { JobService } from 'src/services/job.service';
 
 @ApiTags('Job')
 @Controller('jobs')
-@Authenticated({ admin: true })
 export class JobController {
   constructor(private service: JobService) {}
 
   @Get()
+  @Authenticated({ admin: true })
   getAllJobsStatus(): Promise<AllJobStatusResponseDto> {
     return this.service.getAllJobsStatus();
   }
 
   @Put(':id')
+  @Authenticated({ admin: true })
   sendJobCommand(@Param() { id }: JobIdParamDto, @Body() dto: JobCommandDto): Promise<JobStatusDto> {
     return this.service.handleCommand(id, dto);
   }
diff --git a/server/src/controllers/library.controller.ts b/server/src/controllers/library.controller.ts
index 70d357187e..74a4f73d2a 100644
--- a/server/src/controllers/library.controller.ts
+++ b/server/src/controllers/library.controller.ts
@@ -10,39 +10,42 @@ import {
   ValidateLibraryDto,
   ValidateLibraryResponseDto,
 } from 'src/dtos/library.dto';
-import { AdminRoute, Authenticated } from 'src/middleware/auth.guard';
+import { Authenticated } from 'src/middleware/auth.guard';
 import { LibraryService } from 'src/services/library.service';
 import { UUIDParamDto } from 'src/validation';
 
 @ApiTags('Library')
 @Controller('library')
-@Authenticated()
-@AdminRoute()
 export class LibraryController {
   constructor(private service: LibraryService) {}
 
   @Get()
+  @Authenticated({ admin: true })
   getAllLibraries(@Query() dto: SearchLibraryDto): Promise<LibraryResponseDto[]> {
     return this.service.getAll(dto);
   }
 
   @Post()
+  @Authenticated({ admin: true })
   createLibrary(@Body() dto: CreateLibraryDto): Promise<LibraryResponseDto> {
     return this.service.create(dto);
   }
 
   @Put(':id')
+  @Authenticated({ admin: true })
   updateLibrary(@Param() { id }: UUIDParamDto, @Body() dto: UpdateLibraryDto): Promise<LibraryResponseDto> {
     return this.service.update(id, dto);
   }
 
   @Get(':id')
+  @Authenticated({ admin: true })
   getLibrary(@Param() { id }: UUIDParamDto): Promise<LibraryResponseDto> {
     return this.service.get(id);
   }
 
   @Post(':id/validate')
   @HttpCode(200)
+  @Authenticated({ admin: true })
   // TODO: change endpoint to validate current settings instead
   validate(@Param() { id }: UUIDParamDto, @Body() dto: ValidateLibraryDto): Promise<ValidateLibraryResponseDto> {
     return this.service.validate(id, dto);
@@ -50,23 +53,27 @@ export class LibraryController {
 
   @Delete(':id')
   @HttpCode(HttpStatus.NO_CONTENT)
+  @Authenticated({ admin: true })
   deleteLibrary(@Param() { id }: UUIDParamDto): Promise<void> {
     return this.service.delete(id);
   }
 
   @Get(':id/statistics')
+  @Authenticated({ admin: true })
   getLibraryStatistics(@Param() { id }: UUIDParamDto): Promise<LibraryStatsResponseDto> {
     return this.service.getStatistics(id);
   }
 
   @Post(':id/scan')
   @HttpCode(HttpStatus.NO_CONTENT)
+  @Authenticated({ admin: true })
   scanLibrary(@Param() { id }: UUIDParamDto, @Body() dto: ScanLibraryDto) {
     return this.service.queueScan(id, dto);
   }
 
   @Post(':id/removeOffline')
   @HttpCode(HttpStatus.NO_CONTENT)
+  @Authenticated({ admin: true })
   removeOfflineFiles(@Param() { id }: UUIDParamDto) {
     return this.service.queueRemoveOffline(id);
   }
diff --git a/server/src/controllers/memory.controller.ts b/server/src/controllers/memory.controller.ts
index 771d705942..4b779c60f2 100644
--- a/server/src/controllers/memory.controller.ts
+++ b/server/src/controllers/memory.controller.ts
@@ -9,26 +9,29 @@ import { UUIDParamDto } from 'src/validation';
 
 @ApiTags('Memory')
 @Controller('memories')
-@Authenticated()
 export class MemoryController {
   constructor(private service: MemoryService) {}
 
   @Get()
+  @Authenticated()
   searchMemories(@Auth() auth: AuthDto): Promise<MemoryResponseDto[]> {
     return this.service.search(auth);
   }
 
   @Post()
+  @Authenticated()
   createMemory(@Auth() auth: AuthDto, @Body() dto: MemoryCreateDto): Promise<MemoryResponseDto> {
     return this.service.create(auth, dto);
   }
 
   @Get(':id')
+  @Authenticated()
   getMemory(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<MemoryResponseDto> {
     return this.service.get(auth, id);
   }
 
   @Put(':id')
+  @Authenticated()
   updateMemory(
     @Auth() auth: AuthDto,
     @Param() { id }: UUIDParamDto,
@@ -39,11 +42,13 @@ export class MemoryController {
 
   @Delete(':id')
   @HttpCode(HttpStatus.NO_CONTENT)
+  @Authenticated()
   deleteMemory(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<void> {
     return this.service.remove(auth, id);
   }
 
   @Put(':id/assets')
+  @Authenticated()
   addMemoryAssets(
     @Auth() auth: AuthDto,
     @Param() { id }: UUIDParamDto,
@@ -54,6 +59,7 @@ export class MemoryController {
 
   @Delete(':id/assets')
   @HttpCode(HttpStatus.OK)
+  @Authenticated()
   removeMemoryAssets(
     @Auth() auth: AuthDto,
     @Body() dto: BulkIdsDto,
diff --git a/server/src/controllers/oauth.controller.ts b/server/src/controllers/oauth.controller.ts
index d87fb11d88..3b498c7ddd 100644
--- a/server/src/controllers/oauth.controller.ts
+++ b/server/src/controllers/oauth.controller.ts
@@ -11,17 +11,15 @@ import {
   OAuthConfigDto,
 } from 'src/dtos/auth.dto';
 import { UserResponseDto } from 'src/dtos/user.dto';
-import { Auth, Authenticated, GetLoginDetails, PublicRoute } from 'src/middleware/auth.guard';
+import { Auth, Authenticated, GetLoginDetails } from 'src/middleware/auth.guard';
 import { AuthService, LoginDetails } from 'src/services/auth.service';
 import { respondWithCookie } from 'src/utils/response';
 
 @ApiTags('OAuth')
 @Controller('oauth')
-@Authenticated()
 export class OAuthController {
   constructor(private service: AuthService) {}
 
-  @PublicRoute()
   @Get('mobile-redirect')
   @Redirect()
   redirectOAuthToMobile(@Req() request: Request) {
@@ -31,13 +29,11 @@ export class OAuthController {
     };
   }
 
-  @PublicRoute()
   @Post('authorize')
   startOAuth(@Body() dto: OAuthConfigDto): Promise<OAuthAuthorizeResponseDto> {
     return this.service.authorize(dto);
   }
 
-  @PublicRoute()
   @Post('callback')
   async finishOAuth(
     @Res({ passthrough: true }) res: Response,
@@ -56,11 +52,13 @@ export class OAuthController {
   }
 
   @Post('link')
+  @Authenticated()
   linkOAuthAccount(@Auth() auth: AuthDto, @Body() dto: OAuthCallbackDto): Promise<UserResponseDto> {
     return this.service.link(auth, dto);
   }
 
   @Post('unlink')
+  @Authenticated()
   unlinkOAuthAccount(@Auth() auth: AuthDto): Promise<UserResponseDto> {
     return this.service.unlink(auth);
   }
diff --git a/server/src/controllers/partner.controller.ts b/server/src/controllers/partner.controller.ts
index f654a72637..1faf82898c 100644
--- a/server/src/controllers/partner.controller.ts
+++ b/server/src/controllers/partner.controller.ts
@@ -9,23 +9,25 @@ import { UUIDParamDto } from 'src/validation';
 
 @ApiTags('Partner')
 @Controller('partner')
-@Authenticated()
 export class PartnerController {
   constructor(private service: PartnerService) {}
 
   @Get()
   @ApiQuery({ name: 'direction', type: 'string', enum: PartnerDirection, required: true })
+  @Authenticated()
   // TODO: remove 'direction' and convert to full query dto
   getPartners(@Auth() auth: AuthDto, @Query('direction') direction: PartnerDirection): Promise<PartnerResponseDto[]> {
     return this.service.getAll(auth, direction);
   }
 
   @Post(':id')
+  @Authenticated()
   createPartner(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<PartnerResponseDto> {
     return this.service.create(auth, id);
   }
 
   @Put(':id')
+  @Authenticated()
   updatePartner(
     @Auth() auth: AuthDto,
     @Param() { id }: UUIDParamDto,
@@ -35,6 +37,7 @@ export class PartnerController {
   }
 
   @Delete(':id')
+  @Authenticated()
   removePartner(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<void> {
     return this.service.remove(auth, id);
   }
diff --git a/server/src/controllers/person.controller.ts b/server/src/controllers/person.controller.ts
index c9128a1f7f..dc87825927 100644
--- a/server/src/controllers/person.controller.ts
+++ b/server/src/controllers/person.controller.ts
@@ -22,31 +22,35 @@ import { UUIDParamDto } from 'src/validation';
 
 @ApiTags('Person')
 @Controller('person')
-@Authenticated()
 export class PersonController {
   constructor(private service: PersonService) {}
 
   @Get()
+  @Authenticated()
   getAllPeople(@Auth() auth: AuthDto, @Query() withHidden: PersonSearchDto): Promise<PeopleResponseDto> {
     return this.service.getAll(auth, withHidden);
   }
 
   @Post()
+  @Authenticated()
   createPerson(@Auth() auth: AuthDto, @Body() dto: PersonCreateDto): Promise<PersonResponseDto> {
     return this.service.create(auth, dto);
   }
 
   @Put()
+  @Authenticated()
   updatePeople(@Auth() auth: AuthDto, @Body() dto: PeopleUpdateDto): Promise<BulkIdResponseDto[]> {
     return this.service.updateAll(auth, dto);
   }
 
   @Get(':id')
+  @Authenticated()
   getPerson(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<PersonResponseDto> {
     return this.service.getById(auth, id);
   }
 
   @Put(':id')
+  @Authenticated()
   updatePerson(
     @Auth() auth: AuthDto,
     @Param() { id }: UUIDParamDto,
@@ -56,12 +60,14 @@ export class PersonController {
   }
 
   @Get(':id/statistics')
+  @Authenticated()
   getPersonStatistics(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<PersonStatisticsResponseDto> {
     return this.service.getStatistics(auth, id);
   }
 
   @Get(':id/thumbnail')
   @FileResponse()
+  @Authenticated()
   async getPersonThumbnail(
     @Res() res: Response,
     @Next() next: NextFunction,
@@ -72,11 +78,13 @@ export class PersonController {
   }
 
   @Get(':id/assets')
+  @Authenticated()
   getPersonAssets(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<AssetResponseDto[]> {
     return this.service.getAssets(auth, id);
   }
 
   @Put(':id/reassign')
+  @Authenticated()
   reassignFaces(
     @Auth() auth: AuthDto,
     @Param() { id }: UUIDParamDto,
@@ -86,6 +94,7 @@ export class PersonController {
   }
 
   @Post(':id/merge')
+  @Authenticated()
   mergePerson(
     @Auth() auth: AuthDto,
     @Param() { id }: UUIDParamDto,
diff --git a/server/src/controllers/search.controller.ts b/server/src/controllers/search.controller.ts
index ce0d0f646d..688ff1c138 100644
--- a/server/src/controllers/search.controller.ts
+++ b/server/src/controllers/search.controller.ts
@@ -18,43 +18,49 @@ import { SearchService } from 'src/services/search.service';
 
 @ApiTags('Search')
 @Controller('search')
-@Authenticated()
 export class SearchController {
   constructor(private service: SearchService) {}
 
   @Post('metadata')
   @HttpCode(HttpStatus.OK)
+  @Authenticated()
   searchMetadata(@Auth() auth: AuthDto, @Body() dto: MetadataSearchDto): Promise<SearchResponseDto> {
     return this.service.searchMetadata(auth, dto);
   }
 
   @Post('smart')
   @HttpCode(HttpStatus.OK)
+  @Authenticated()
   searchSmart(@Auth() auth: AuthDto, @Body() dto: SmartSearchDto): Promise<SearchResponseDto> {
     return this.service.searchSmart(auth, dto);
   }
 
   @Get('explore')
+  @Authenticated()
   getExploreData(@Auth() auth: AuthDto): Promise<SearchExploreResponseDto[]> {
     return this.service.getExploreData(auth) as Promise<SearchExploreResponseDto[]>;
   }
 
   @Get('person')
+  @Authenticated()
   searchPerson(@Auth() auth: AuthDto, @Query() dto: SearchPeopleDto): Promise<PersonResponseDto[]> {
     return this.service.searchPerson(auth, dto);
   }
 
   @Get('places')
+  @Authenticated()
   searchPlaces(@Query() dto: SearchPlacesDto): Promise<PlacesResponseDto[]> {
     return this.service.searchPlaces(dto);
   }
 
   @Get('cities')
+  @Authenticated()
   getAssetsByCity(@Auth() auth: AuthDto): Promise<AssetResponseDto[]> {
     return this.service.getAssetsByCity(auth);
   }
 
   @Get('suggestions')
+  @Authenticated()
   getSearchSuggestions(@Auth() auth: AuthDto, @Query() dto: SearchSuggestionRequestDto): Promise<string[]> {
     return this.service.getSearchSuggestions(auth, dto);
   }
diff --git a/server/src/controllers/server-info.controller.ts b/server/src/controllers/server-info.controller.ts
index 35e5e17594..758c50299a 100644
--- a/server/src/controllers/server-info.controller.ts
+++ b/server/src/controllers/server-info.controller.ts
@@ -10,57 +10,51 @@ import {
   ServerThemeDto,
   ServerVersionResponseDto,
 } from 'src/dtos/server-info.dto';
-import { AdminRoute, Authenticated, PublicRoute } from 'src/middleware/auth.guard';
+import { Authenticated } from 'src/middleware/auth.guard';
 import { ServerInfoService } from 'src/services/server-info.service';
 
 @ApiTags('Server Info')
 @Controller('server-info')
-@Authenticated()
 export class ServerInfoController {
   constructor(private service: ServerInfoService) {}
 
   @Get()
+  @Authenticated()
   getServerInfo(): Promise<ServerInfoResponseDto> {
     return this.service.getInfo();
   }
 
-  @PublicRoute()
   @Get('ping')
   pingServer(): ServerPingResponse {
     return this.service.ping();
   }
 
-  @PublicRoute()
   @Get('version')
   getServerVersion(): ServerVersionResponseDto {
     return this.service.getVersion();
   }
 
-  @PublicRoute()
   @Get('features')
   getServerFeatures(): Promise<ServerFeaturesDto> {
     return this.service.getFeatures();
   }
 
-  @PublicRoute()
   @Get('theme')
   getTheme(): Promise<ServerThemeDto> {
     return this.service.getTheme();
   }
 
-  @PublicRoute()
   @Get('config')
   getServerConfig(): Promise<ServerConfigDto> {
     return this.service.getConfig();
   }
 
-  @AdminRoute()
+  @Authenticated({ admin: true })
   @Get('statistics')
   getServerStatistics(): Promise<ServerStatsResponseDto> {
     return this.service.getStatistics();
   }
 
-  @PublicRoute()
   @Get('media-types')
   getSupportedMediaTypes(): ServerMediaTypesResponseDto {
     return this.service.getSupportedMediaTypes();
diff --git a/server/src/controllers/session.controller.ts b/server/src/controllers/session.controller.ts
index 552afcdf5a..a1fb4779a5 100644
--- a/server/src/controllers/session.controller.ts
+++ b/server/src/controllers/session.controller.ts
@@ -8,23 +8,25 @@ import { UUIDParamDto } from 'src/validation';
 
 @ApiTags('Sessions')
 @Controller('sessions')
-@Authenticated()
 export class SessionController {
   constructor(private service: SessionService) {}
 
   @Get()
+  @Authenticated()
   getSessions(@Auth() auth: AuthDto): Promise<SessionResponseDto[]> {
     return this.service.getAll(auth);
   }
 
   @Delete()
   @HttpCode(HttpStatus.NO_CONTENT)
+  @Authenticated()
   deleteAllSessions(@Auth() auth: AuthDto): Promise<void> {
     return this.service.deleteAll(auth);
   }
 
   @Delete(':id')
   @HttpCode(HttpStatus.NO_CONTENT)
+  @Authenticated()
   deleteSession(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<void> {
     return this.service.delete(auth, id);
   }
diff --git a/server/src/controllers/shared-link.controller.ts b/server/src/controllers/shared-link.controller.ts
index 58f2939b93..64d3e38e7e 100644
--- a/server/src/controllers/shared-link.controller.ts
+++ b/server/src/controllers/shared-link.controller.ts
@@ -10,7 +10,7 @@ import {
   SharedLinkPasswordDto,
   SharedLinkResponseDto,
 } from 'src/dtos/shared-link.dto';
-import { Auth, Authenticated, GetLoginDetails, SharedLinkRoute } from 'src/middleware/auth.guard';
+import { Auth, Authenticated, GetLoginDetails } from 'src/middleware/auth.guard';
 import { LoginDetails } from 'src/services/auth.service';
 import { SharedLinkService } from 'src/services/shared-link.service';
 import { respondWithCookie } from 'src/utils/response';
@@ -18,17 +18,17 @@ import { UUIDParamDto } from 'src/validation';
 
 @ApiTags('Shared Link')
 @Controller('shared-link')
-@Authenticated()
 export class SharedLinkController {
   constructor(private service: SharedLinkService) {}
 
   @Get()
+  @Authenticated()
   getAllSharedLinks(@Auth() auth: AuthDto): Promise<SharedLinkResponseDto[]> {
     return this.service.getAll(auth);
   }
 
-  @SharedLinkRoute()
   @Get('me')
+  @Authenticated({ sharedLink: true })
   async getMySharedLink(
     @Auth() auth: AuthDto,
     @Query() dto: SharedLinkPasswordDto,
@@ -48,16 +48,19 @@ export class SharedLinkController {
   }
 
   @Get(':id')
+  @Authenticated()
   getSharedLinkById(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<SharedLinkResponseDto> {
     return this.service.get(auth, id);
   }
 
   @Post()
+  @Authenticated()
   createSharedLink(@Auth() auth: AuthDto, @Body() dto: SharedLinkCreateDto) {
     return this.service.create(auth, dto);
   }
 
   @Patch(':id')
+  @Authenticated()
   updateSharedLink(
     @Auth() auth: AuthDto,
     @Param() { id }: UUIDParamDto,
@@ -67,12 +70,13 @@ export class SharedLinkController {
   }
 
   @Delete(':id')
+  @Authenticated()
   removeSharedLink(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<void> {
     return this.service.remove(auth, id);
   }
 
-  @SharedLinkRoute()
   @Put(':id/assets')
+  @Authenticated({ sharedLink: true })
   addSharedLinkAssets(
     @Auth() auth: AuthDto,
     @Param() { id }: UUIDParamDto,
@@ -81,8 +85,8 @@ export class SharedLinkController {
     return this.service.addAssets(auth, id, dto);
   }
 
-  @SharedLinkRoute()
   @Delete(':id/assets')
+  @Authenticated({ sharedLink: true })
   removeSharedLinkAssets(
     @Auth() auth: AuthDto,
     @Param() { id }: UUIDParamDto,
diff --git a/server/src/controllers/sync.controller.ts b/server/src/controllers/sync.controller.ts
index 63757f73f3..4d970a7102 100644
--- a/server/src/controllers/sync.controller.ts
+++ b/server/src/controllers/sync.controller.ts
@@ -8,18 +8,19 @@ import { SyncService } from 'src/services/sync.service';
 
 @ApiTags('Sync')
 @Controller('sync')
-@Authenticated()
 export class SyncController {
   constructor(private service: SyncService) {}
 
   @Post('full-sync')
   @HttpCode(HttpStatus.OK)
+  @Authenticated()
   getFullSyncForUser(@Auth() auth: AuthDto, @Body() dto: AssetFullSyncDto): Promise<AssetResponseDto[]> {
     return this.service.getFullSync(auth, dto);
   }
 
   @Post('delta-sync')
   @HttpCode(HttpStatus.OK)
+  @Authenticated()
   getDeltaSync(@Auth() auth: AuthDto, @Body() dto: AssetDeltaSyncDto): Promise<AssetDeltaSyncResponseDto> {
     return this.service.getDeltaSync(auth, dto);
   }
diff --git a/server/src/controllers/system-config.controller.ts b/server/src/controllers/system-config.controller.ts
index 08da743191..bf9e8495f7 100644
--- a/server/src/controllers/system-config.controller.ts
+++ b/server/src/controllers/system-config.controller.ts
@@ -1,37 +1,39 @@
 import { Body, Controller, Get, Put, Query } from '@nestjs/common';
 import { ApiTags } from '@nestjs/swagger';
 import { MapThemeDto, SystemConfigDto, SystemConfigTemplateStorageOptionDto } from 'src/dtos/system-config.dto';
-import { AdminRoute, Authenticated, SharedLinkRoute } from 'src/middleware/auth.guard';
+import { Authenticated } from 'src/middleware/auth.guard';
 import { SystemConfigService } from 'src/services/system-config.service';
 
 @ApiTags('System Config')
 @Controller('system-config')
-@Authenticated({ admin: true })
 export class SystemConfigController {
   constructor(private service: SystemConfigService) {}
 
   @Get()
+  @Authenticated({ admin: true })
   getConfig(): Promise<SystemConfigDto> {
     return this.service.getConfig();
   }
 
   @Get('defaults')
+  @Authenticated({ admin: true })
   getConfigDefaults(): SystemConfigDto {
     return this.service.getDefaults();
   }
 
   @Put()
+  @Authenticated({ admin: true })
   updateConfig(@Body() dto: SystemConfigDto): Promise<SystemConfigDto> {
     return this.service.updateConfig(dto);
   }
 
   @Get('storage-template-options')
+  @Authenticated({ admin: true })
   getStorageTemplateOptions(): SystemConfigTemplateStorageOptionDto {
     return this.service.getStorageTemplateOptions();
   }
 
-  @AdminRoute(false)
-  @SharedLinkRoute()
+  @Authenticated({ sharedLink: true })
   @Get('map/style.json')
   getMapStyle(@Query() dto: MapThemeDto) {
     return this.service.getMapStyle(dto.theme);
diff --git a/server/src/controllers/system-metadata.controller.ts b/server/src/controllers/system-metadata.controller.ts
index 7f186fec03..90e9f5b6a8 100644
--- a/server/src/controllers/system-metadata.controller.ts
+++ b/server/src/controllers/system-metadata.controller.ts
@@ -6,22 +6,24 @@ import { SystemMetadataService } from 'src/services/system-metadata.service';
 
 @ApiTags('System Metadata')
 @Controller('system-metadata')
-@Authenticated({ admin: true })
 export class SystemMetadataController {
   constructor(private service: SystemMetadataService) {}
 
   @Get('admin-onboarding')
+  @Authenticated({ admin: true })
   getAdminOnboarding(): Promise<AdminOnboardingUpdateDto> {
     return this.service.getAdminOnboarding();
   }
 
   @Post('admin-onboarding')
   @HttpCode(HttpStatus.NO_CONTENT)
+  @Authenticated({ admin: true })
   updateAdminOnboarding(@Body() dto: AdminOnboardingUpdateDto): Promise<void> {
     return this.service.updateAdminOnboarding(dto);
   }
 
   @Get('reverse-geocoding-state')
+  @Authenticated({ admin: true })
   getReverseGeocodingState(): Promise<ReverseGeocodingStateResponseDto> {
     return this.service.getReverseGeocodingState();
   }
diff --git a/server/src/controllers/tag.controller.ts b/server/src/controllers/tag.controller.ts
index 1caed8d528..2a46fdec71 100644
--- a/server/src/controllers/tag.controller.ts
+++ b/server/src/controllers/tag.controller.ts
@@ -11,41 +11,47 @@ import { UUIDParamDto } from 'src/validation';
 
 @ApiTags('Tag')
 @Controller('tag')
-@Authenticated()
 export class TagController {
   constructor(private service: TagService) {}
 
   @Post()
+  @Authenticated()
   createTag(@Auth() auth: AuthDto, @Body() dto: CreateTagDto): Promise<TagResponseDto> {
     return this.service.create(auth, dto);
   }
 
   @Get()
+  @Authenticated()
   getAllTags(@Auth() auth: AuthDto): Promise<TagResponseDto[]> {
     return this.service.getAll(auth);
   }
 
   @Get(':id')
+  @Authenticated()
   getTagById(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<TagResponseDto> {
     return this.service.getById(auth, id);
   }
 
   @Patch(':id')
+  @Authenticated()
   updateTag(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto, @Body() dto: UpdateTagDto): Promise<TagResponseDto> {
     return this.service.update(auth, id, dto);
   }
 
   @Delete(':id')
+  @Authenticated()
   deleteTag(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<void> {
     return this.service.remove(auth, id);
   }
 
   @Get(':id/assets')
+  @Authenticated()
   getTagAssets(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<AssetResponseDto[]> {
     return this.service.getAssets(auth, id);
   }
 
   @Put(':id/assets')
+  @Authenticated()
   tagAssets(
     @Auth() auth: AuthDto,
     @Param() { id }: UUIDParamDto,
@@ -55,6 +61,7 @@ export class TagController {
   }
 
   @Delete(':id/assets')
+  @Authenticated()
   untagAssets(
     @Auth() auth: AuthDto,
     @Body() dto: AssetIdsDto,
diff --git a/server/src/controllers/timeline.controller.ts b/server/src/controllers/timeline.controller.ts
index 173c6738de..53c62f70ed 100644
--- a/server/src/controllers/timeline.controller.ts
+++ b/server/src/controllers/timeline.controller.ts
@@ -8,17 +8,16 @@ import { TimelineService } from 'src/services/timeline.service';
 
 @ApiTags('Timeline')
 @Controller('timeline')
-@Authenticated()
 export class TimelineController {
   constructor(private service: TimelineService) {}
 
-  @Authenticated({ isShared: true })
+  @Authenticated({ sharedLink: true })
   @Get('buckets')
   getTimeBuckets(@Auth() auth: AuthDto, @Query() dto: TimeBucketDto): Promise<TimeBucketResponseDto[]> {
     return this.service.getTimeBuckets(auth, dto);
   }
 
-  @Authenticated({ isShared: true })
+  @Authenticated({ sharedLink: true })
   @Get('bucket')
   getTimeBucket(@Auth() auth: AuthDto, @Query() dto: TimeBucketAssetDto): Promise<AssetResponseDto[]> {
     return this.service.getTimeBucket(auth, dto) as Promise<AssetResponseDto[]>;
diff --git a/server/src/controllers/trash.controller.ts b/server/src/controllers/trash.controller.ts
index 25df3543cc..eae49d4ad1 100644
--- a/server/src/controllers/trash.controller.ts
+++ b/server/src/controllers/trash.controller.ts
@@ -7,24 +7,26 @@ import { TrashService } from 'src/services/trash.service';
 
 @ApiTags('Trash')
 @Controller('trash')
-@Authenticated()
 export class TrashController {
   constructor(private service: TrashService) {}
 
   @Post('empty')
   @HttpCode(HttpStatus.NO_CONTENT)
+  @Authenticated()
   emptyTrash(@Auth() auth: AuthDto): Promise<void> {
     return this.service.empty(auth);
   }
 
   @Post('restore')
   @HttpCode(HttpStatus.NO_CONTENT)
+  @Authenticated()
   restoreTrash(@Auth() auth: AuthDto): Promise<void> {
     return this.service.restore(auth);
   }
 
   @Post('restore/assets')
   @HttpCode(HttpStatus.NO_CONTENT)
+  @Authenticated()
   restoreAssets(@Auth() auth: AuthDto, @Body() dto: BulkIdsDto): Promise<void> {
     return this.service.restoreAssets(auth, dto);
   }
diff --git a/server/src/controllers/user.controller.ts b/server/src/controllers/user.controller.ts
index c108e88527..8b1abbb26d 100644
--- a/server/src/controllers/user.controller.ts
+++ b/server/src/controllers/user.controller.ts
@@ -19,7 +19,7 @@ import { NextFunction, Response } from 'express';
 import { AuthDto } from 'src/dtos/auth.dto';
 import { CreateProfileImageDto, CreateProfileImageResponseDto } from 'src/dtos/user-profile.dto';
 import { CreateUserDto, DeleteUserDto, UpdateUserDto, UserResponseDto } from 'src/dtos/user.dto';
-import { AdminRoute, Auth, Authenticated, FileResponse } from 'src/middleware/auth.guard';
+import { Auth, Authenticated, FileResponse } from 'src/middleware/auth.guard';
 import { FileUploadInterceptor, Route } from 'src/middleware/file-upload.interceptor';
 import { UserService } from 'src/services/user.service';
 import { sendFile } from 'src/utils/file';
@@ -27,39 +27,42 @@ import { UUIDParamDto } from 'src/validation';
 
 @ApiTags('User')
 @Controller(Route.USER)
-@Authenticated()
 export class UserController {
   constructor(private service: UserService) {}
 
   @Get()
+  @Authenticated()
   getAllUsers(@Auth() auth: AuthDto, @Query('isAll') isAll: boolean): Promise<UserResponseDto[]> {
     return this.service.getAll(auth, isAll);
   }
 
   @Get('info/:id')
+  @Authenticated()
   getUserById(@Param() { id }: UUIDParamDto): Promise<UserResponseDto> {
     return this.service.get(id);
   }
 
   @Get('me')
+  @Authenticated()
   getMyUserInfo(@Auth() auth: AuthDto): Promise<UserResponseDto> {
     return this.service.getMe(auth);
   }
 
-  @AdminRoute()
   @Post()
+  @Authenticated({ admin: true })
   createUser(@Body() createUserDto: CreateUserDto): Promise<UserResponseDto> {
     return this.service.create(createUserDto);
   }
 
   @Delete('profile-image')
   @HttpCode(HttpStatus.NO_CONTENT)
+  @Authenticated()
   deleteProfileImage(@Auth() auth: AuthDto): Promise<void> {
     return this.service.deleteProfileImage(auth);
   }
 
-  @AdminRoute()
   @Delete(':id')
+  @Authenticated({ admin: true })
   deleteUser(
     @Auth() auth: AuthDto,
     @Param() { id }: UUIDParamDto,
@@ -68,14 +71,15 @@ export class UserController {
     return this.service.delete(auth, id, dto);
   }
 
-  @AdminRoute()
   @Post(':id/restore')
+  @Authenticated({ admin: true })
   restoreUser(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<UserResponseDto> {
     return this.service.restore(auth, id);
   }
 
   // TODO: replace with @Put(':id')
   @Put()
+  @Authenticated()
   updateUser(@Auth() auth: AuthDto, @Body() updateUserDto: UpdateUserDto): Promise<UserResponseDto> {
     return this.service.update(auth, updateUserDto);
   }
@@ -84,6 +88,7 @@ export class UserController {
   @ApiConsumes('multipart/form-data')
   @ApiBody({ description: 'A new avatar for the user', type: CreateProfileImageDto })
   @Post('profile-image')
+  @Authenticated()
   createProfileImage(
     @Auth() auth: AuthDto,
     @UploadedFile() fileInfo: Express.Multer.File,
@@ -93,6 +98,7 @@ export class UserController {
 
   @Get('profile-image/:id')
   @FileResponse()
+  @Authenticated()
   async getProfileImage(@Res() res: Response, @Next() next: NextFunction, @Param() { id }: UUIDParamDto) {
     await sendFile(res, next, () => this.service.getProfileImage(id));
   }
diff --git a/server/src/dtos/auth.dto.ts b/server/src/dtos/auth.dto.ts
index 73843729b9..c7f7893e20 100644
--- a/server/src/dtos/auth.dto.ts
+++ b/server/src/dtos/auth.dto.ts
@@ -17,10 +17,14 @@ export enum ImmichHeader {
   API_KEY = 'x-api-key',
   USER_TOKEN = 'x-immich-user-token',
   SESSION_TOKEN = 'x-immich-session-token',
-  SHARED_LINK_TOKEN = 'x-immich-share-key',
+  SHARED_LINK_KEY = 'x-immich-share-key',
   CHECKSUM = 'x-immich-checksum',
 }
 
+export enum ImmichQuery {
+  SHARED_LINK_KEY = 'key',
+}
+
 export type CookieResponse = {
   isSecure: boolean;
   values: Array<{ key: ImmichCookie; value: string }>;
diff --git a/server/src/middleware/auth.guard.ts b/server/src/middleware/auth.guard.ts
index 59e82c00aa..cbea439489 100644
--- a/server/src/middleware/auth.guard.ts
+++ b/server/src/middleware/auth.guard.ts
@@ -10,7 +10,7 @@ import {
 import { Reflector } from '@nestjs/core';
 import { ApiBearerAuth, ApiCookieAuth, ApiOkResponse, ApiQuery, ApiSecurity } from '@nestjs/swagger';
 import { Request } from 'express';
-import { AuthDto } from 'src/dtos/auth.dto';
+import { AuthDto, ImmichQuery } from 'src/dtos/auth.dto';
 import { ILoggerRepository } from 'src/interfaces/logger.interface';
 import { AuthService, LoginDetails } from 'src/services/auth.service';
 import { UAParser } from 'ua-parser-js';
@@ -19,42 +19,30 @@ export enum Metadata {
   AUTH_ROUTE = 'auth_route',
   ADMIN_ROUTE = 'admin_route',
   SHARED_ROUTE = 'shared_route',
-  PUBLIC_SECURITY = 'public_security',
   API_KEY_SECURITY = 'api_key',
 }
 
-export interface AuthenticatedOptions {
-  admin?: true;
-  isShared?: true;
-}
+type AdminRoute = { admin?: true };
+type SharedLinkRoute = { sharedLink?: true };
+type AuthenticatedOptions = AdminRoute | SharedLinkRoute;
 
-export const Authenticated = (options: AuthenticatedOptions = {}) => {
+export const Authenticated = (options?: AuthenticatedOptions): MethodDecorator => {
   const decorators: MethodDecorator[] = [
     ApiBearerAuth(),
     ApiCookieAuth(),
     ApiSecurity(Metadata.API_KEY_SECURITY),
-    SetMetadata(Metadata.AUTH_ROUTE, true),
+    SetMetadata(Metadata.AUTH_ROUTE, options || {}),
   ];
 
-  if (options.admin) {
-    decorators.push(AdminRoute());
-  }
-
-  if (options.isShared) {
-    decorators.push(SharedLinkRoute());
+  if ((options as SharedLinkRoute)?.sharedLink) {
+    decorators.push(ApiQuery({ name: ImmichQuery.SHARED_LINK_KEY, type: String, required: false }));
   }
 
   return applyDecorators(...decorators);
 };
 
-export const PublicRoute = () =>
-  applyDecorators(SetMetadata(Metadata.AUTH_ROUTE, false), ApiSecurity(Metadata.PUBLIC_SECURITY));
-export const SharedLinkRoute = () =>
-  applyDecorators(SetMetadata(Metadata.SHARED_ROUTE, true), ApiQuery({ name: 'key', type: String, required: false }));
-export const AdminRoute = (value = true) => SetMetadata(Metadata.ADMIN_ROUTE, value);
-
 export const Auth = createParamDecorator((data, context: ExecutionContext): AuthDto => {
-  return context.switchToHttp().getRequest<{ user: AuthDto }>().user;
+  return context.switchToHttp().getRequest<AuthenticatedRequest>().user;
 });
 
 export const FileResponse = () =>
@@ -93,25 +81,22 @@ export class AuthGuard implements CanActivate {
   }
 
   async canActivate(context: ExecutionContext): Promise<boolean> {
-    const targets = [context.getHandler(), context.getClass()];
+    const targets = [context.getHandler()];
 
-    const isAuthRoute = this.reflector.getAllAndOverride(Metadata.AUTH_ROUTE, targets);
-    const isAdminRoute = this.reflector.getAllAndOverride(Metadata.ADMIN_ROUTE, targets);
-    const isSharedRoute = this.reflector.getAllAndOverride(Metadata.SHARED_ROUTE, targets);
-
-    if (!isAuthRoute) {
+    const options = this.reflector.getAllAndOverride<AuthenticatedOptions | undefined>(Metadata.AUTH_ROUTE, targets);
+    if (!options) {
       return true;
     }
 
     const request = context.switchToHttp().getRequest<AuthRequest>();
 
     const authDto = await this.authService.validate(request.headers, request.query as Record<string, string>);
-    if (authDto.sharedLink && !isSharedRoute) {
+    if (authDto.sharedLink && !(options as SharedLinkRoute).sharedLink) {
       this.logger.warn(`Denied access to non-shared route: ${request.path}`);
       return false;
     }
 
-    if (isAdminRoute && !authDto.user.isAdmin) {
+    if (!authDto.user.isAdmin && (options as AdminRoute).admin) {
       this.logger.warn(`Denied access to admin only route: ${request.path}`);
       return false;
     }
diff --git a/server/src/services/auth.service.ts b/server/src/services/auth.service.ts
index 72fee12f45..b2c524decd 100644
--- a/server/src/services/auth.service.ts
+++ b/server/src/services/auth.service.ts
@@ -149,7 +149,7 @@ export class AuthService {
   }
 
   async validate(headers: IncomingHttpHeaders, params: Record<string, string>): Promise<AuthDto> {
-    const shareKey = (headers[ImmichHeader.SHARED_LINK_TOKEN] || params.key) as string;
+    const shareKey = (headers[ImmichHeader.SHARED_LINK_KEY] || params.key) as string;
     const session = (headers[ImmichHeader.USER_TOKEN] ||
       headers[ImmichHeader.SESSION_TOKEN] ||
       params.sessionKey ||
diff --git a/server/src/utils/misc.ts b/server/src/utils/misc.ts
index 8262b6024b..95eefe7039 100644
--- a/server/src/utils/misc.ts
+++ b/server/src/utils/misc.ts
@@ -103,10 +103,6 @@ const patchOpenAPI = (document: OpenAPIObject) => {
         continue;
       }
 
-      if ((operation.security || []).some((item) => !!item[Metadata.PUBLIC_SECURITY])) {
-        delete operation.security;
-      }
-
       if (operation.summary === '') {
         delete operation.summary;
       }