diff --git a/i18n/en.json b/i18n/en.json
index 239936471d..f1ab30a6d0 100644
--- a/i18n/en.json
+++ b/i18n/en.json
@@ -192,26 +192,22 @@
     "oauth_auto_register": "Auto register",
     "oauth_auto_register_description": "Automatically register new users after signing in with OAuth",
     "oauth_button_text": "Button text",
-    "oauth_client_id": "Client ID",
-    "oauth_client_secret": "Client Secret",
+    "oauth_client_secret_description": "Required if PKCE (Proof Key for Code Exchange) is not supported by the OAuth provider",
     "oauth_enable_description": "Login with OAuth",
-    "oauth_issuer_url": "Issuer URL",
     "oauth_mobile_redirect_uri": "Mobile redirect URI",
     "oauth_mobile_redirect_uri_override": "Mobile redirect URI override",
     "oauth_mobile_redirect_uri_override_description": "Enable when OAuth provider does not allow a mobile URI, like '{callback}'",
-    "oauth_profile_signing_algorithm": "Profile signing algorithm",
-    "oauth_profile_signing_algorithm_description": "Algorithm used to sign the user profile.",
-    "oauth_scope": "Scope",
     "oauth_settings": "OAuth",
     "oauth_settings_description": "Manage OAuth login settings",
     "oauth_settings_more_details": "For more details about this feature, refer to the <link>docs</link>.",
-    "oauth_signing_algorithm": "Signing algorithm",
     "oauth_storage_label_claim": "Storage label claim",
     "oauth_storage_label_claim_description": "Automatically set the user's storage label to the value of this claim.",
     "oauth_storage_quota_claim": "Storage quota claim",
     "oauth_storage_quota_claim_description": "Automatically set the user's storage quota to the value of this claim.",
     "oauth_storage_quota_default": "Default storage quota (GiB)",
     "oauth_storage_quota_default_description": "Quota in GiB to be used when no claim is provided (Enter 0 for unlimited quota).",
+    "oauth_timeout": "Request Timeout",
+    "oauth_timeout_description": "Timeout for requests in milliseconds",
     "offline_paths": "Offline Paths",
     "offline_paths_description": "These results may be due to manual deletion of files that are not part of an external library.",
     "password_enable_description": "Login with email and password",
diff --git a/mobile/openapi/README.md b/mobile/openapi/README.md
index b8ea4b924c..d46945f640 100644
--- a/mobile/openapi/README.md
+++ b/mobile/openapi/README.md
@@ -377,6 +377,7 @@ Class | Method | HTTP request | Description
  - [OAuthAuthorizeResponseDto](doc//OAuthAuthorizeResponseDto.md)
  - [OAuthCallbackDto](doc//OAuthCallbackDto.md)
  - [OAuthConfigDto](doc//OAuthConfigDto.md)
+ - [OAuthTokenEndpointAuthMethod](doc//OAuthTokenEndpointAuthMethod.md)
  - [OnThisDayDto](doc//OnThisDayDto.md)
  - [PartnerDirection](doc//PartnerDirection.md)
  - [PartnerResponseDto](doc//PartnerResponseDto.md)
diff --git a/mobile/openapi/lib/api.dart b/mobile/openapi/lib/api.dart
index e845099bd2..ba64363c97 100644
--- a/mobile/openapi/lib/api.dart
+++ b/mobile/openapi/lib/api.dart
@@ -178,6 +178,7 @@ part 'model/notification_update_dto.dart';
 part 'model/o_auth_authorize_response_dto.dart';
 part 'model/o_auth_callback_dto.dart';
 part 'model/o_auth_config_dto.dart';
+part 'model/o_auth_token_endpoint_auth_method.dart';
 part 'model/on_this_day_dto.dart';
 part 'model/partner_direction.dart';
 part 'model/partner_response_dto.dart';
diff --git a/mobile/openapi/lib/api_client.dart b/mobile/openapi/lib/api_client.dart
index 7586cc1ae2..6abe576aca 100644
--- a/mobile/openapi/lib/api_client.dart
+++ b/mobile/openapi/lib/api_client.dart
@@ -410,6 +410,8 @@ class ApiClient {
           return OAuthCallbackDto.fromJson(value);
         case 'OAuthConfigDto':
           return OAuthConfigDto.fromJson(value);
+        case 'OAuthTokenEndpointAuthMethod':
+          return OAuthTokenEndpointAuthMethodTypeTransformer().decode(value);
         case 'OnThisDayDto':
           return OnThisDayDto.fromJson(value);
         case 'PartnerDirection':
diff --git a/mobile/openapi/lib/api_helper.dart b/mobile/openapi/lib/api_helper.dart
index cc517d48ab..5f9d15c089 100644
--- a/mobile/openapi/lib/api_helper.dart
+++ b/mobile/openapi/lib/api_helper.dart
@@ -106,6 +106,9 @@ String parameterToString(dynamic value) {
   if (value is NotificationType) {
     return NotificationTypeTypeTransformer().encode(value).toString();
   }
+  if (value is OAuthTokenEndpointAuthMethod) {
+    return OAuthTokenEndpointAuthMethodTypeTransformer().encode(value).toString();
+  }
   if (value is PartnerDirection) {
     return PartnerDirectionTypeTransformer().encode(value).toString();
   }
diff --git a/mobile/openapi/lib/model/o_auth_token_endpoint_auth_method.dart b/mobile/openapi/lib/model/o_auth_token_endpoint_auth_method.dart
new file mode 100644
index 0000000000..fc528888b3
--- /dev/null
+++ b/mobile/openapi/lib/model/o_auth_token_endpoint_auth_method.dart
@@ -0,0 +1,85 @@
+//
+// AUTO-GENERATED FILE, DO NOT MODIFY!
+//
+// @dart=2.18
+
+// ignore_for_file: unused_element, unused_import
+// ignore_for_file: always_put_required_named_parameters_first
+// ignore_for_file: constant_identifier_names
+// ignore_for_file: lines_longer_than_80_chars
+
+part of openapi.api;
+
+
+class OAuthTokenEndpointAuthMethod {
+  /// Instantiate a new enum with the provided [value].
+  const OAuthTokenEndpointAuthMethod._(this.value);
+
+  /// The underlying value of this enum member.
+  final String value;
+
+  @override
+  String toString() => value;
+
+  String toJson() => value;
+
+  static const post = OAuthTokenEndpointAuthMethod._(r'client_secret_post');
+  static const basic = OAuthTokenEndpointAuthMethod._(r'client_secret_basic');
+
+  /// List of all possible values in this [enum][OAuthTokenEndpointAuthMethod].
+  static const values = <OAuthTokenEndpointAuthMethod>[
+    post,
+    basic,
+  ];
+
+  static OAuthTokenEndpointAuthMethod? fromJson(dynamic value) => OAuthTokenEndpointAuthMethodTypeTransformer().decode(value);
+
+  static List<OAuthTokenEndpointAuthMethod> listFromJson(dynamic json, {bool growable = false,}) {
+    final result = <OAuthTokenEndpointAuthMethod>[];
+    if (json is List && json.isNotEmpty) {
+      for (final row in json) {
+        final value = OAuthTokenEndpointAuthMethod.fromJson(row);
+        if (value != null) {
+          result.add(value);
+        }
+      }
+    }
+    return result.toList(growable: growable);
+  }
+}
+
+/// Transformation class that can [encode] an instance of [OAuthTokenEndpointAuthMethod] to String,
+/// and [decode] dynamic data back to [OAuthTokenEndpointAuthMethod].
+class OAuthTokenEndpointAuthMethodTypeTransformer {
+  factory OAuthTokenEndpointAuthMethodTypeTransformer() => _instance ??= const OAuthTokenEndpointAuthMethodTypeTransformer._();
+
+  const OAuthTokenEndpointAuthMethodTypeTransformer._();
+
+  String encode(OAuthTokenEndpointAuthMethod data) => data.value;
+
+  /// Decodes a [dynamic value][data] to a OAuthTokenEndpointAuthMethod.
+  ///
+  /// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully,
+  /// then null is returned. However, if [allowNull] is false and the [dynamic value][data]
+  /// cannot be decoded successfully, then an [UnimplementedError] is thrown.
+  ///
+  /// The [allowNull] is very handy when an API changes and a new enum value is added or removed,
+  /// and users are still using an old app with the old code.
+  OAuthTokenEndpointAuthMethod? decode(dynamic data, {bool allowNull = true}) {
+    if (data != null) {
+      switch (data) {
+        case r'client_secret_post': return OAuthTokenEndpointAuthMethod.post;
+        case r'client_secret_basic': return OAuthTokenEndpointAuthMethod.basic;
+        default:
+          if (!allowNull) {
+            throw ArgumentError('Unknown enum value to decode: $data');
+          }
+      }
+    }
+    return null;
+  }
+
+  /// Singleton [OAuthTokenEndpointAuthMethodTypeTransformer] instance.
+  static OAuthTokenEndpointAuthMethodTypeTransformer? _instance;
+}
+
diff --git a/mobile/openapi/lib/model/system_config_o_auth_dto.dart b/mobile/openapi/lib/model/system_config_o_auth_dto.dart
index 9125bb7bba..24384a47b1 100644
--- a/mobile/openapi/lib/model/system_config_o_auth_dto.dart
+++ b/mobile/openapi/lib/model/system_config_o_auth_dto.dart
@@ -28,6 +28,8 @@ class SystemConfigOAuthDto {
     required this.signingAlgorithm,
     required this.storageLabelClaim,
     required this.storageQuotaClaim,
+    required this.timeout,
+    required this.tokenEndpointAuthMethod,
   });
 
   bool autoLaunch;
@@ -61,6 +63,11 @@ class SystemConfigOAuthDto {
 
   String storageQuotaClaim;
 
+  /// Minimum value: 1
+  int timeout;
+
+  OAuthTokenEndpointAuthMethod tokenEndpointAuthMethod;
+
   @override
   bool operator ==(Object other) => identical(this, other) || other is SystemConfigOAuthDto &&
     other.autoLaunch == autoLaunch &&
@@ -77,7 +84,9 @@ class SystemConfigOAuthDto {
     other.scope == scope &&
     other.signingAlgorithm == signingAlgorithm &&
     other.storageLabelClaim == storageLabelClaim &&
-    other.storageQuotaClaim == storageQuotaClaim;
+    other.storageQuotaClaim == storageQuotaClaim &&
+    other.timeout == timeout &&
+    other.tokenEndpointAuthMethod == tokenEndpointAuthMethod;
 
   @override
   int get hashCode =>
@@ -96,10 +105,12 @@ class SystemConfigOAuthDto {
     (scope.hashCode) +
     (signingAlgorithm.hashCode) +
     (storageLabelClaim.hashCode) +
-    (storageQuotaClaim.hashCode);
+    (storageQuotaClaim.hashCode) +
+    (timeout.hashCode) +
+    (tokenEndpointAuthMethod.hashCode);
 
   @override
-  String toString() => 'SystemConfigOAuthDto[autoLaunch=$autoLaunch, autoRegister=$autoRegister, buttonText=$buttonText, clientId=$clientId, clientSecret=$clientSecret, defaultStorageQuota=$defaultStorageQuota, enabled=$enabled, issuerUrl=$issuerUrl, mobileOverrideEnabled=$mobileOverrideEnabled, mobileRedirectUri=$mobileRedirectUri, profileSigningAlgorithm=$profileSigningAlgorithm, scope=$scope, signingAlgorithm=$signingAlgorithm, storageLabelClaim=$storageLabelClaim, storageQuotaClaim=$storageQuotaClaim]';
+  String toString() => 'SystemConfigOAuthDto[autoLaunch=$autoLaunch, autoRegister=$autoRegister, buttonText=$buttonText, clientId=$clientId, clientSecret=$clientSecret, defaultStorageQuota=$defaultStorageQuota, enabled=$enabled, issuerUrl=$issuerUrl, mobileOverrideEnabled=$mobileOverrideEnabled, mobileRedirectUri=$mobileRedirectUri, profileSigningAlgorithm=$profileSigningAlgorithm, scope=$scope, signingAlgorithm=$signingAlgorithm, storageLabelClaim=$storageLabelClaim, storageQuotaClaim=$storageQuotaClaim, timeout=$timeout, tokenEndpointAuthMethod=$tokenEndpointAuthMethod]';
 
   Map<String, dynamic> toJson() {
     final json = <String, dynamic>{};
@@ -118,6 +129,8 @@ class SystemConfigOAuthDto {
       json[r'signingAlgorithm'] = this.signingAlgorithm;
       json[r'storageLabelClaim'] = this.storageLabelClaim;
       json[r'storageQuotaClaim'] = this.storageQuotaClaim;
+      json[r'timeout'] = this.timeout;
+      json[r'tokenEndpointAuthMethod'] = this.tokenEndpointAuthMethod;
     return json;
   }
 
@@ -145,6 +158,8 @@ class SystemConfigOAuthDto {
         signingAlgorithm: mapValueOfType<String>(json, r'signingAlgorithm')!,
         storageLabelClaim: mapValueOfType<String>(json, r'storageLabelClaim')!,
         storageQuotaClaim: mapValueOfType<String>(json, r'storageQuotaClaim')!,
+        timeout: mapValueOfType<int>(json, r'timeout')!,
+        tokenEndpointAuthMethod: OAuthTokenEndpointAuthMethod.fromJson(json[r'tokenEndpointAuthMethod'])!,
       );
     }
     return null;
@@ -207,6 +222,8 @@ class SystemConfigOAuthDto {
     'signingAlgorithm',
     'storageLabelClaim',
     'storageQuotaClaim',
+    'timeout',
+    'tokenEndpointAuthMethod',
   };
 }
 
diff --git a/open-api/immich-openapi-specs.json b/open-api/immich-openapi-specs.json
index f4ec929373..826af5a2ec 100644
--- a/open-api/immich-openapi-specs.json
+++ b/open-api/immich-openapi-specs.json
@@ -10824,6 +10824,13 @@
         ],
         "type": "object"
       },
+      "OAuthTokenEndpointAuthMethod": {
+        "enum": [
+          "client_secret_post",
+          "client_secret_basic"
+        ],
+        "type": "string"
+      },
       "OnThisDayDto": {
         "properties": {
           "year": {
@@ -13404,6 +13411,17 @@
           },
           "storageQuotaClaim": {
             "type": "string"
+          },
+          "timeout": {
+            "minimum": 1,
+            "type": "integer"
+          },
+          "tokenEndpointAuthMethod": {
+            "allOf": [
+              {
+                "$ref": "#/components/schemas/OAuthTokenEndpointAuthMethod"
+              }
+            ]
           }
         },
         "required": [
@@ -13421,7 +13439,9 @@
           "scope",
           "signingAlgorithm",
           "storageLabelClaim",
-          "storageQuotaClaim"
+          "storageQuotaClaim",
+          "timeout",
+          "tokenEndpointAuthMethod"
         ],
         "type": "object"
       },
diff --git a/open-api/typescript-sdk/src/fetch-client.ts b/open-api/typescript-sdk/src/fetch-client.ts
index 647c5c4ada..743eeadf03 100644
--- a/open-api/typescript-sdk/src/fetch-client.ts
+++ b/open-api/typescript-sdk/src/fetch-client.ts
@@ -1315,6 +1315,8 @@ export type SystemConfigOAuthDto = {
     signingAlgorithm: string;
     storageLabelClaim: string;
     storageQuotaClaim: string;
+    timeout: number;
+    tokenEndpointAuthMethod: OAuthTokenEndpointAuthMethod;
 };
 export type SystemConfigPasswordLoginDto = {
     enabled: boolean;
@@ -3859,6 +3861,10 @@ export enum LogLevel {
     Error = "error",
     Fatal = "fatal"
 }
+export enum OAuthTokenEndpointAuthMethod {
+    ClientSecretPost = "client_secret_post",
+    ClientSecretBasic = "client_secret_basic"
+}
 export enum TimeBucketSize {
     Day = "DAY",
     Month = "MONTH"
diff --git a/server/src/config.ts b/server/src/config.ts
index 566adbd693..a9fdffbd62 100644
--- a/server/src/config.ts
+++ b/server/src/config.ts
@@ -5,6 +5,7 @@ import {
   CQMode,
   ImageFormat,
   LogLevel,
+  OAuthTokenEndpointAuthMethod,
   QueueName,
   ToneMapping,
   TranscodeHWAccel,
@@ -96,6 +97,8 @@ export interface SystemConfig {
     scope: string;
     signingAlgorithm: string;
     profileSigningAlgorithm: string;
+    tokenEndpointAuthMethod: OAuthTokenEndpointAuthMethod;
+    timeout: number;
     storageLabelClaim: string;
     storageQuotaClaim: string;
   };
@@ -260,6 +263,8 @@ export const defaults = Object.freeze<SystemConfig>({
     profileSigningAlgorithm: 'none',
     storageLabelClaim: 'preferred_username',
     storageQuotaClaim: 'immich_quota',
+    tokenEndpointAuthMethod: OAuthTokenEndpointAuthMethod.CLIENT_SECRET_POST,
+    timeout: 30_000,
   },
   passwordLogin: {
     enabled: true,
diff --git a/server/src/dtos/system-config.dto.ts b/server/src/dtos/system-config.dto.ts
index eaef40a5e1..6991baf109 100644
--- a/server/src/dtos/system-config.dto.ts
+++ b/server/src/dtos/system-config.dto.ts
@@ -25,6 +25,7 @@ import {
   Colorspace,
   ImageFormat,
   LogLevel,
+  OAuthTokenEndpointAuthMethod,
   QueueName,
   ToneMapping,
   TranscodeHWAccel,
@@ -33,7 +34,7 @@ import {
   VideoContainer,
 } from 'src/enum';
 import { ConcurrentQueueName } from 'src/types';
-import { IsCronExpression, ValidateBoolean } from 'src/validation';
+import { IsCronExpression, Optional, ValidateBoolean } from 'src/validation';
 
 const isLibraryScanEnabled = (config: SystemConfigLibraryScanDto) => config.enabled;
 const isOAuthEnabled = (config: SystemConfigOAuthDto) => config.enabled;
@@ -344,10 +345,19 @@ class SystemConfigOAuthDto {
   clientId!: string;
 
   @ValidateIf(isOAuthEnabled)
-  @IsNotEmpty()
   @IsString()
   clientSecret!: string;
 
+  @IsEnum(OAuthTokenEndpointAuthMethod)
+  @ApiProperty({ enum: OAuthTokenEndpointAuthMethod, enumName: 'OAuthTokenEndpointAuthMethod' })
+  tokenEndpointAuthMethod!: OAuthTokenEndpointAuthMethod;
+
+  @IsInt()
+  @IsPositive()
+  @Optional()
+  @ApiProperty({ type: 'integer' })
+  timeout!: number;
+
   @IsNumber()
   @Min(0)
   defaultStorageQuota!: number;
diff --git a/server/src/enum.ts b/server/src/enum.ts
index c88e2e942c..4e725e1c13 100644
--- a/server/src/enum.ts
+++ b/server/src/enum.ts
@@ -605,3 +605,8 @@ export enum NotificationType {
   SystemMessage = 'SystemMessage',
   Custom = 'Custom',
 }
+
+export enum OAuthTokenEndpointAuthMethod {
+  CLIENT_SECRET_POST = 'client_secret_post',
+  CLIENT_SECRET_BASIC = 'client_secret_basic',
+}
diff --git a/server/src/repositories/logging.repository.ts b/server/src/repositories/logging.repository.ts
index 05d2d45f4d..2ac3715a50 100644
--- a/server/src/repositories/logging.repository.ts
+++ b/server/src/repositories/logging.repository.ts
@@ -5,7 +5,7 @@ import { Telemetry } from 'src/decorators';
 import { LogLevel } from 'src/enum';
 import { ConfigRepository } from 'src/repositories/config.repository';
 
-type LogDetails = any[];
+type LogDetails = any;
 type LogFunction = () => string;
 
 const LOG_LEVELS = [LogLevel.VERBOSE, LogLevel.DEBUG, LogLevel.LOG, LogLevel.WARN, LogLevel.ERROR, LogLevel.FATAL];
diff --git a/server/src/repositories/oauth.repository.ts b/server/src/repositories/oauth.repository.ts
index d3e0372089..ea9f0b1901 100644
--- a/server/src/repositories/oauth.repository.ts
+++ b/server/src/repositories/oauth.repository.ts
@@ -1,16 +1,19 @@
 import { Injectable, InternalServerErrorException } from '@nestjs/common';
 import type { UserInfoResponse } from 'openid-client' with { 'resolution-mode': 'import' };
+import { OAuthTokenEndpointAuthMethod } from 'src/enum';
 import { LoggingRepository } from 'src/repositories/logging.repository';
 
 export type OAuthConfig = {
   clientId: string;
-  clientSecret: string;
+  clientSecret?: string;
   issuerUrl: string;
   mobileOverrideEnabled: boolean;
   mobileRedirectUri: string;
   profileSigningAlgorithm: string;
   scope: string;
   signingAlgorithm: string;
+  tokenEndpointAuthMethod: OAuthTokenEndpointAuthMethod;
+  timeout: number;
 };
 export type OAuthProfile = UserInfoResponse;
 
@@ -76,12 +79,10 @@ export class OAuthRepository {
         );
       }
 
-      if (error.code === 'OAUTH_INVALID_RESPONSE') {
-        this.logger.warn(`Invalid response from authorization server. Cause: ${error.cause?.message}`);
-        throw error.cause;
-      }
+      this.logger.error(`OAuth login failed: ${error.message}`);
+      this.logger.error(error);
 
-      throw error;
+      throw new Error('OAuth login failed', { cause: error });
     }
   }
 
@@ -103,6 +104,8 @@ export class OAuthRepository {
     clientSecret,
     profileSigningAlgorithm,
     signingAlgorithm,
+    tokenEndpointAuthMethod,
+    timeout,
   }: OAuthConfig) {
     try {
       const { allowInsecureRequests, discovery } = await import('openid-client');
@@ -114,14 +117,38 @@ export class OAuthRepository {
           response_types: ['code'],
           userinfo_signed_response_alg: profileSigningAlgorithm === 'none' ? undefined : profileSigningAlgorithm,
           id_token_signed_response_alg: signingAlgorithm,
-          timeout: 30_000,
         },
-        undefined,
-        { execute: [allowInsecureRequests] },
+        await this.getTokenAuthMethod(tokenEndpointAuthMethod, clientSecret),
+        {
+          execute: [allowInsecureRequests],
+          timeout,
+        },
       );
     } catch (error: any | AggregateError) {
       this.logger.error(`Error in OAuth discovery: ${error}`, error?.stack, error?.errors);
       throw new InternalServerErrorException(`Error in OAuth discovery: ${error}`, { cause: error });
     }
   }
+
+  private async getTokenAuthMethod(tokenEndpointAuthMethod: OAuthTokenEndpointAuthMethod, clientSecret?: string) {
+    const { None, ClientSecretPost, ClientSecretBasic } = await import('openid-client');
+
+    if (!clientSecret) {
+      return None();
+    }
+
+    switch (tokenEndpointAuthMethod) {
+      case OAuthTokenEndpointAuthMethod.CLIENT_SECRET_POST: {
+        return ClientSecretPost(clientSecret);
+      }
+
+      case OAuthTokenEndpointAuthMethod.CLIENT_SECRET_BASIC: {
+        return ClientSecretBasic(clientSecret);
+      }
+
+      default: {
+        return None();
+      }
+    }
+  }
 }
diff --git a/server/src/services/system-config.service.spec.ts b/server/src/services/system-config.service.spec.ts
index 936acf27ad..176e6d6f04 100644
--- a/server/src/services/system-config.service.spec.ts
+++ b/server/src/services/system-config.service.spec.ts
@@ -6,6 +6,7 @@ import {
   CQMode,
   ImageFormat,
   LogLevel,
+  OAuthTokenEndpointAuthMethod,
   QueueName,
   ToneMapping,
   TranscodeHWAccel,
@@ -119,6 +120,8 @@ const updatedConfig = Object.freeze<SystemConfig>({
     scope: 'openid email profile',
     signingAlgorithm: 'RS256',
     profileSigningAlgorithm: 'none',
+    tokenEndpointAuthMethod: OAuthTokenEndpointAuthMethod.CLIENT_SECRET_POST,
+    timeout: 30_000,
     storageLabelClaim: 'preferred_username',
     storageQuotaClaim: 'immich_quota',
   },
diff --git a/web/src/lib/components/admin-page/settings/auth/auth-settings.svelte b/web/src/lib/components/admin-page/settings/auth/auth-settings.svelte
index 67da6bb7f2..b2454b06c3 100644
--- a/web/src/lib/components/admin-page/settings/auth/auth-settings.svelte
+++ b/web/src/lib/components/admin-page/settings/auth/auth-settings.svelte
@@ -1,16 +1,17 @@
 <script lang="ts">
+  import FormatMessage from '$lib/components/i18n/format-message.svelte';
   import ConfirmDialog from '$lib/components/shared-components/dialog/confirm-dialog.svelte';
   import SettingAccordion from '$lib/components/shared-components/settings/setting-accordion.svelte';
   import SettingButtonsRow from '$lib/components/shared-components/settings/setting-buttons-row.svelte';
   import SettingInputField from '$lib/components/shared-components/settings/setting-input-field.svelte';
+  import SettingSelect from '$lib/components/shared-components/settings/setting-select.svelte';
   import SettingSwitch from '$lib/components/shared-components/settings/setting-switch.svelte';
-  import { type SystemConfigDto } from '@immich/sdk';
+  import { SettingInputFieldType } from '$lib/constants';
+  import { OAuthTokenEndpointAuthMethod, type SystemConfigDto } from '@immich/sdk';
   import { isEqual } from 'lodash-es';
+  import { t } from 'svelte-i18n';
   import { fade } from 'svelte/transition';
   import type { SettingsResetEvent, SettingsSaveEvent } from '../admin-settings';
-  import { t } from 'svelte-i18n';
-  import FormatMessage from '$lib/components/i18n/format-message.svelte';
-  import { SettingInputFieldType } from '$lib/constants';
 
   interface Props {
     savedConfig: SystemConfigDto;
@@ -108,7 +109,7 @@
               <hr />
               <SettingInputField
                 inputType={SettingInputFieldType.TEXT}
-                label={$t('admin.oauth_issuer_url').toUpperCase()}
+                label="ISSUER_URL"
                 bind:value={config.oauth.issuerUrl}
                 required={true}
                 disabled={disabled || !config.oauth.enabled}
@@ -117,7 +118,7 @@
 
               <SettingInputField
                 inputType={SettingInputFieldType.TEXT}
-                label={$t('admin.oauth_client_id').toUpperCase()}
+                label="CLIENT_ID"
                 bind:value={config.oauth.clientId}
                 required={true}
                 disabled={disabled || !config.oauth.enabled}
@@ -126,16 +127,30 @@
 
               <SettingInputField
                 inputType={SettingInputFieldType.TEXT}
-                label={$t('admin.oauth_client_secret').toUpperCase()}
+                label="CLIENT_SECRET"
+                description={$t('admin.oauth_client_secret_description')}
                 bind:value={config.oauth.clientSecret}
-                required={true}
                 disabled={disabled || !config.oauth.enabled}
                 isEdited={!(config.oauth.clientSecret == savedConfig.oauth.clientSecret)}
               />
 
+              {#if config.oauth.clientSecret}
+                <SettingSelect
+                  label="TOKEN_ENDPOINT_AUTH_METHOD"
+                  bind:value={config.oauth.tokenEndpointAuthMethod}
+                  disabled={disabled || !config.oauth.enabled || !config.oauth.clientSecret}
+                  isEdited={!(config.oauth.tokenEndpointAuthMethod == savedConfig.oauth.tokenEndpointAuthMethod)}
+                  options={[
+                    { value: OAuthTokenEndpointAuthMethod.ClientSecretPost, text: 'client_secret_post' },
+                    { value: OAuthTokenEndpointAuthMethod.ClientSecretBasic, text: 'client_secret_basic' },
+                  ]}
+                  name="tokenEndpointAuthMethod"
+                />
+              {/if}
+
               <SettingInputField
                 inputType={SettingInputFieldType.TEXT}
-                label={$t('admin.oauth_scope').toUpperCase()}
+                label="SCOPE"
                 bind:value={config.oauth.scope}
                 required={true}
                 disabled={disabled || !config.oauth.enabled}
@@ -144,7 +159,7 @@
 
               <SettingInputField
                 inputType={SettingInputFieldType.TEXT}
-                label={$t('admin.oauth_signing_algorithm').toUpperCase()}
+                label="ID_TOKEN_SIGNED_RESPONSE_ALG"
                 bind:value={config.oauth.signingAlgorithm}
                 required={true}
                 disabled={disabled || !config.oauth.enabled}
@@ -153,14 +168,23 @@
 
               <SettingInputField
                 inputType={SettingInputFieldType.TEXT}
-                label={$t('admin.oauth_profile_signing_algorithm').toUpperCase()}
-                description={$t('admin.oauth_profile_signing_algorithm_description')}
+                label="USERINFO_SIGNED_RESPONSE_ALG"
                 bind:value={config.oauth.profileSigningAlgorithm}
                 required={true}
                 disabled={disabled || !config.oauth.enabled}
                 isEdited={!(config.oauth.profileSigningAlgorithm == savedConfig.oauth.profileSigningAlgorithm)}
               />
 
+              <SettingInputField
+                inputType={SettingInputFieldType.TEXT}
+                label={$t('admin.oauth_timeout').toUpperCase()}
+                description={$t('admin.oauth_timeout_description')}
+                required={true}
+                bind:value={config.oauth.timeout}
+                disabled={disabled || !config.oauth.enabled}
+                isEdited={!(config.oauth.timeout == savedConfig.oauth.timeout)}
+              />
+
               <SettingInputField
                 inputType={SettingInputFieldType.TEXT}
                 label={$t('admin.oauth_storage_label_claim').toUpperCase()}
diff --git a/web/src/lib/components/shared-components/settings/setting-input-field.svelte b/web/src/lib/components/shared-components/settings/setting-input-field.svelte
index 946f40c08d..2cab6e95eb 100644
--- a/web/src/lib/components/shared-components/settings/setting-input-field.svelte
+++ b/web/src/lib/components/shared-components/settings/setting-input-field.svelte
@@ -1,15 +1,15 @@
 <script lang="ts">
+  import { SettingInputFieldType } from '$lib/constants';
+  import { onMount, tick, type Snippet } from 'svelte';
+  import { t } from 'svelte-i18n';
   import { quintOut } from 'svelte/easing';
   import type { FormEventHandler } from 'svelte/elements';
   import { fly } from 'svelte/transition';
   import PasswordField from '../password-field.svelte';
-  import { t } from 'svelte-i18n';
-  import { onMount, tick, type Snippet } from 'svelte';
-  import { SettingInputFieldType } from '$lib/constants';
 
   interface Props {
     inputType: SettingInputFieldType;
-    value: string | number;
+    value: string | number | undefined;
     min?: number;
     max?: number;
     step?: string;
@@ -147,7 +147,7 @@
       name={label}
       autocomplete={passwordAutocomplete}
       {required}
-      password={value.toString()}
+      password={(value || '').toString()}
       onInput={(passwordValue) => (value = passwordValue)}
       {disabled}
       {title}
diff --git a/web/src/lib/components/shared-components/settings/setting-select.svelte b/web/src/lib/components/shared-components/settings/setting-select.svelte
index 87260e6d6a..e13d2387fb 100644
--- a/web/src/lib/components/shared-components/settings/setting-select.svelte
+++ b/web/src/lib/components/shared-components/settings/setting-select.svelte
@@ -1,12 +1,12 @@
 <script lang="ts">
-  import { quintOut } from 'svelte/easing';
-  import { fly } from 'svelte/transition';
-  import { t } from 'svelte-i18n';
   import Icon from '$lib/components/elements/icon.svelte';
   import { mdiChevronDown } from '@mdi/js';
+  import { t } from 'svelte-i18n';
+  import { quintOut } from 'svelte/easing';
+  import { fly } from 'svelte/transition';
 
   interface Props {
-    value: string | number;
+    value: string | number | undefined;
     options: { value: string | number; text: string }[];
     label?: string;
     desc?: string;