From 5263281f4e551f09213cdc7890f04c594358e216 Mon Sep 17 00:00:00 2001
From: Alex Maltsev <and1sscsgo@gmail.com>
Date: Mon, 7 Oct 2024 14:33:36 +0300
Subject: [PATCH 1/3] Added simple api-key authorization for post cache
 handler.

---
 .../handlers/cache/PostCacheHandler.java      | 16 +++++-
 .../org/prebid/cache/routers/ApiConfig.java   |  2 +
 .../cache/handlers/PostCacheHandlerTests.java | 52 ++++++++++++++-----
 3 files changed, 57 insertions(+), 13 deletions(-)

diff --git a/src/main/java/org/prebid/cache/handlers/cache/PostCacheHandler.java b/src/main/java/org/prebid/cache/handlers/cache/PostCacheHandler.java
index ff4d997..42867da 100644
--- a/src/main/java/org/prebid/cache/handlers/cache/PostCacheHandler.java
+++ b/src/main/java/org/prebid/cache/handlers/cache/PostCacheHandler.java
@@ -22,6 +22,7 @@
 import org.prebid.cache.model.ResponseObject;
 import org.prebid.cache.repository.CacheConfig;
 import org.prebid.cache.repository.ReactiveRepository;
+import org.prebid.cache.routers.ApiConfig;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.http.HttpStatus;
@@ -49,6 +50,7 @@ public class PostCacheHandler extends CacheHandler {
 
     private static final String UUID_KEY = "uuid";
     private static final String SECONDARY_CACHE_KEY = "secondaryCache";
+    private static final String API_KEY_HEADER = "x-pbc-api-key";
 
     private final ReactiveRepository<PayloadWrapper, String> repository;
     private final CacheConfig config;
@@ -57,6 +59,7 @@ public class PostCacheHandler extends CacheHandler {
     private final Map<String, WebClient> webClients = new HashMap<>();
     private final ObjectMapper objectMapper = new ObjectMapper();
     private final CircuitBreaker circuitBreaker;
+    private final ApiConfig apiConfig;
 
     @Autowired
     public PostCacheHandler(final ReactiveRepository<PayloadWrapper, String> repository,
@@ -64,7 +67,8 @@ public PostCacheHandler(final ReactiveRepository<PayloadWrapper, String> reposit
                             final MetricsRecorder metricsRecorder,
                             final PrebidServerResponseBuilder builder,
                             final CircuitBreaker webClientCircuitBreaker,
-                            @Value("${sampling.rate:0.01}") final Double samplingRate) {
+                            @Value("${sampling.rate:0.01}") final Double samplingRate,
+                            final ApiConfig apiConfig) {
 
         super(samplingRate);
         this.metricsRecorder = metricsRecorder;
@@ -77,9 +81,14 @@ public PostCacheHandler(final ReactiveRepository<PayloadWrapper, String> reposit
         this.builder = builder;
         this.metricTagPrefix = "write";
         this.circuitBreaker = webClientCircuitBreaker;
+        this.apiConfig = apiConfig;
     }
 
     public Mono<ServerResponse> save(final ServerRequest request) {
+        if (!isWriteAllowed(request)) {
+            return ServerResponse.status(HttpStatus.UNAUTHORIZED).build();
+        }
+
         metricsRecorder.markMeterForTag(this.metricTagPrefix, MetricsRecorder.MeasurementTag.REQUEST);
         final var timerContext = metricsRecorder.createRequestTimerForServiceType(type);
 
@@ -119,6 +128,11 @@ public Mono<ServerResponse> save(final ServerRequest request) {
         return finalizeResult(responseMono, request, timerContext);
     }
 
+    private boolean isWriteAllowed(final ServerRequest request) {
+        return !apiConfig.isCacheWriteSecured()
+                || StringUtils.equals(request.headers().firstHeader(API_KEY_HEADER), apiConfig.getApiKey());
+    }
+
     private Function<PayloadTransfer, PayloadWrapper> payloadWrapperTransformer() {
         return transfer -> PayloadWrapper.builder()
                 .id(RandomUUID.extractUUID(transfer))
diff --git a/src/main/java/org/prebid/cache/routers/ApiConfig.java b/src/main/java/org/prebid/cache/routers/ApiConfig.java
index cf5216f..18f77e8 100644
--- a/src/main/java/org/prebid/cache/routers/ApiConfig.java
+++ b/src/main/java/org/prebid/cache/routers/ApiConfig.java
@@ -19,6 +19,8 @@ public class ApiConfig {
     @NotEmpty
     private String cachePath;
 
+    private boolean cacheWriteSecured;
+
     @NotEmpty
     private String storagePath;
 
diff --git a/src/test/java/org/prebid/cache/handlers/PostCacheHandlerTests.java b/src/test/java/org/prebid/cache/handlers/PostCacheHandlerTests.java
index d251c11..b5386f6 100644
--- a/src/test/java/org/prebid/cache/handlers/PostCacheHandlerTests.java
+++ b/src/test/java/org/prebid/cache/handlers/PostCacheHandlerTests.java
@@ -81,6 +81,9 @@ class PostCacheHandlerTests extends CacheHandlerTests {
     @MockBean
     ReactiveRepository<PayloadWrapper, String> repository;
 
+    @MockBean
+    ApiConfig apiConfig;
+
     @Value("${sampling.rate:2.0}")
     Double samplingRate;
 
@@ -92,7 +95,8 @@ void testVerifyError() {
                 metricsRecorder,
                 builder,
                 webClientCircuitBreaker,
-                samplingRate);
+                samplingRate,
+                apiConfig);
         verifyJacksonError(handler);
         verifyRepositoryError(handler);
     }
@@ -116,7 +120,7 @@ void testVerifySave() {
         given(repository.save(PAYLOAD_WRAPPER)).willReturn(Mono.just(PAYLOAD_WRAPPER));
 
         final PostCacheHandler handler = new PostCacheHandler(repository, cacheConfig, metricsRecorder, builder,
-                webClientCircuitBreaker, samplingRate);
+                webClientCircuitBreaker, samplingRate, apiConfig);
 
         final Mono<RequestObject> request = Mono.just(RequestObject.of(Collections.singletonList(PAYLOAD_TRANSFER)));
         final MockServerRequest requestMono = MockServerRequest.builder()
@@ -144,7 +148,7 @@ void testSecondaryCacheSuccess() {
                 .willReturn(aResponse().withBody("{\"responses\":[{\"uuid\":\"2be04ba5-8f9b-4a1e-8100-d573c40312f8\"}]}")));
 
         final PostCacheHandler handler = new PostCacheHandler(repository, cacheConfig, metricsRecorder, builder,
-                webClientCircuitBreaker, samplingRate);
+                webClientCircuitBreaker, samplingRate, apiConfig);
 
         final Mono<RequestObject> request = Mono.just(RequestObject.of(Collections.singletonList(PAYLOAD_TRANSFER)));
         final MockServerRequest requestMono = MockServerRequest.builder()
@@ -173,11 +177,11 @@ void testSecondaryCacheSuccess() {
     void testExternalUUIDInvalid() {
         //given
         final var cacheConfigLocal = new CacheConfig(cacheConfig.getPrefix(), cacheConfig.getExpirySec(),
-            cacheConfig.getTimeoutMs(),
-            cacheConfig.getMinExpiry(), cacheConfig.getMaxExpiry(),
-            false, Collections.emptyList(), cacheConfig.getSecondaryCachePath(), 100, 100, "example.com", "http");
+                cacheConfig.getTimeoutMs(),
+                cacheConfig.getMinExpiry(), cacheConfig.getMaxExpiry(),
+                false, Collections.emptyList(), cacheConfig.getSecondaryCachePath(), 100, 100, "example.com", "http");
         final var handler = new PostCacheHandler(repository, cacheConfigLocal, metricsRecorder, builder,
-            webClientCircuitBreaker, samplingRate);
+                webClientCircuitBreaker, samplingRate, apiConfig);
 
         final Mono<RequestObject> request = Mono.just(RequestObject.of(Collections.singletonList(PAYLOAD_TRANSFER)));
         final MockServerRequest requestMono = MockServerRequest.builder()
@@ -208,7 +212,7 @@ void testUUIDDuplication() {
                 5, cacheConfig.getMaxExpiry(), cacheConfig.isAllowExternalUUID(),
                 Collections.emptyList(), cacheConfig.getSecondaryCachePath(), 100, 100, "example.com", "http");
         final PostCacheHandler handler = new PostCacheHandler(repository, cacheConfigLocal, metricsRecorder, builder,
-                webClientCircuitBreaker, samplingRate);
+                webClientCircuitBreaker, samplingRate, apiConfig);
 
         final Mono<RequestObject> request = Mono.just(RequestObject.of(Collections.singletonList(PAYLOAD_TRANSFER)));
         final MockServerRequest requestMono = MockServerRequest.builder()
@@ -229,11 +233,35 @@ void testUUIDDuplication() {
         final Mono<ServerResponse> responseMonoSecond = handler.save(requestMono);
 
         final Consumer<ServerResponse> consumerSecond = serverResponse ->
-            assertEquals(400, serverResponse.statusCode().value());
+                assertEquals(400, serverResponse.statusCode().value());
 
         StepVerifier.create(responseMonoSecond)
-            .consumeNextWith(consumerSecond)
-            .expectComplete()
-            .verify();
+                .consumeNextWith(consumerSecond)
+                .expectComplete()
+                .verify();
+    }
+
+    @Test
+    void testAuthorization() {
+        given(apiConfig.isCacheWriteSecured()).willReturn(true);
+        given(apiConfig.getApiKey()).willReturn("api-key");
+
+        var handler = new PostCacheHandler(
+                repository,
+                cacheConfig,
+                metricsRecorder,
+                builder,
+                webClientCircuitBreaker,
+                samplingRate,
+                apiConfig);
+
+        var request = MockServerRequest.builder()
+                .method(HttpMethod.POST)
+                .build();
+
+        StepVerifier.create(handler.save(request))
+                .consumeNextWith(serverResponse -> assertEquals(401, serverResponse.statusCode().value()))
+                .expectComplete()
+                .verify();
     }
 }

From 6e696b43c3d2057c184b5ce37aab7d1bea3faf8a Mon Sep 17 00:00:00 2001
From: Alex Maltsev <and1sscsgo@gmail.com>
Date: Mon, 7 Oct 2024 14:55:32 +0300
Subject: [PATCH 2/3] Fixed styling.

---
 .../java/org/prebid/cache/handlers/PostCacheHandlerTests.java | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/test/java/org/prebid/cache/handlers/PostCacheHandlerTests.java b/src/test/java/org/prebid/cache/handlers/PostCacheHandlerTests.java
index b5386f6..2004470 100644
--- a/src/test/java/org/prebid/cache/handlers/PostCacheHandlerTests.java
+++ b/src/test/java/org/prebid/cache/handlers/PostCacheHandlerTests.java
@@ -246,7 +246,7 @@ void testAuthorization() {
         given(apiConfig.isCacheWriteSecured()).willReturn(true);
         given(apiConfig.getApiKey()).willReturn("api-key");
 
-        var handler = new PostCacheHandler(
+        final var handler = new PostCacheHandler(
                 repository,
                 cacheConfig,
                 metricsRecorder,
@@ -255,7 +255,7 @@ void testAuthorization() {
                 samplingRate,
                 apiConfig);
 
-        var request = MockServerRequest.builder()
+        final var request = MockServerRequest.builder()
                 .method(HttpMethod.POST)
                 .build();
 

From 1191c5b6b69f0d8381840122bfbb84c6b3fcdcef Mon Sep 17 00:00:00 2001
From: osulzhenko <125548596+osulzhenko@users.noreply.github.com>
Date: Tue, 11 Feb 2025 17:16:26 +0200
Subject: [PATCH 3/3] Add  functional tests for prevent external (#142)

* Add draft functional tests for prevent external POST
---
 .../cache/functional/AerospikeCacheSpec.kt    |  26 +--
 .../cache/functional/ApacheIgniteCacheSpec.kt |  22 +--
 .../functional/AuthenticationCacheSpec.kt     | 184 ++++++++++++++++++
 .../org/prebid/cache/functional/BaseSpec.kt   |   2 +-
 .../cache/functional/GeneralCacheSpec.kt      |  12 +-
 .../cache/functional/ProxyCacheHostSpec.kt    |   4 +-
 .../prebid/cache/functional/RedisCacheSpec.kt |   8 +-
 .../cache/functional/SecondaryCacheSpec.kt    |   2 +-
 .../functional/service/PrebidCacheApi.kt      |  37 +++-
 .../PrebidCacheContainerConfig.kt             |  73 ++++---
 10 files changed, 302 insertions(+), 68 deletions(-)
 create mode 100644 src/test/kotlin/org/prebid/cache/functional/AuthenticationCacheSpec.kt

diff --git a/src/test/kotlin/org/prebid/cache/functional/AerospikeCacheSpec.kt b/src/test/kotlin/org/prebid/cache/functional/AerospikeCacheSpec.kt
index f1d3d97..4eb6831 100644
--- a/src/test/kotlin/org/prebid/cache/functional/AerospikeCacheSpec.kt
+++ b/src/test/kotlin/org/prebid/cache/functional/AerospikeCacheSpec.kt
@@ -25,7 +25,7 @@ class AerospikeCacheSpec : ShouldSpec({
 
     should("throw an exception when cache record is absent in Aerospike repository") {
         // given: Prebid cache config
-        val config = prebidCacheConfig.getBaseAerospikeConfig("true")
+        val config = prebidCacheConfig.getBaseAerospikeConfig(true)
         val cachePrefix = config["cache.prefix"]
 
         // when: GET cache endpoint with random UUID is called
@@ -42,7 +42,7 @@ class AerospikeCacheSpec : ShouldSpec({
     should("rethrow an exception from Aerospike cache server when such happens") {
         // given: Prebid Cache with not matched to Aerospike server namespace
         val unmatchedNamespace = getRandomString()
-        val config = prebidCacheConfig.getBaseAerospikeConfig("true", unmatchedNamespace)
+        val config = prebidCacheConfig.getBaseAerospikeConfig(true, unmatchedNamespace)
         val prebidCacheApi = BaseSpec.getPrebidCacheApi(config)
 
         // and: Default request object
@@ -64,8 +64,8 @@ class AerospikeCacheSpec : ShouldSpec({
     should("throw an exception when aerospike.prevent_UUID_duplication=true and request with already existing UUID is send") {
         // given: Prebid Cache with aerospike.prevent_UUID_duplication=true property
         val prebidCacheApi = BaseSpec.getPrebidCacheApi(
-            prebidCacheConfig.getBaseAerospikeConfig("true") +
-                    prebidCacheConfig.getAerospikePreventUuidDuplicationConfig("true")
+            prebidCacheConfig.getBaseAerospikeConfig(true) +
+                    prebidCacheConfig.getAerospikePreventUuidDuplicationConfig(true)
         )
 
         // and: First request object with set UUID
@@ -73,10 +73,10 @@ class AerospikeCacheSpec : ShouldSpec({
         val xmlPayloadTransfer = PayloadTransfer.getDefaultXmlPayloadTransfer().apply { key = uuid }
         val requestObject = RequestObject.of(xmlPayloadTransfer)
 
-        // and: First request object is saved to Aerospike cache
+        // and: The first request object is saved to Aerospike cache
         prebidCacheApi.postCache(requestObject)
 
-        // and: Second request object with already existing UUID is prepared
+        // and: A second request object with already existing UUID is prepared
         val jsonPayloadTransfer = PayloadTransfer.getDefaultJsonPayloadTransfer().apply { key = uuid }
         val secondRequestObject = RequestObject.of(jsonPayloadTransfer)
 
@@ -93,8 +93,8 @@ class AerospikeCacheSpec : ShouldSpec({
     should("return back two request UUIDs when allow_external_UUID=true and 2 payload transfers were successfully cached in Aerospike") {
         // given: Prebid Cache with allow_external_UUID=true property
         val prebidCacheApi = BaseSpec.getPrebidCacheApi(
-            prebidCacheConfig.getBaseAerospikeConfig("true") +
-                    prebidCacheConfig.getAerospikePreventUuidDuplicationConfig("false")
+            prebidCacheConfig.getBaseAerospikeConfig(true) +
+                    prebidCacheConfig.getAerospikePreventUuidDuplicationConfig(false)
         )
 
         // and: Request object with 2 payload transfers and set UUIDs is prepared
@@ -105,7 +105,7 @@ class AerospikeCacheSpec : ShouldSpec({
         // when: POST cache endpoint is called
         val responseObject: ResponseObject = prebidCacheApi.postCache(requestObject)
 
-        // then: UUIDs from request object are returned
+        // then: UUIDs from a request object are returned
         responseObject.responses.isEmpty() shouldBe false
         responseObject.responses.size shouldBe 2
 
@@ -117,8 +117,8 @@ class AerospikeCacheSpec : ShouldSpec({
     should("update existing cache record when aerospike.prevent_UUID_duplication=false and request with already existing UUID is send") {
         // given: Prebid Cache with aerospike.prevent_UUID_duplication=false
         val prebidCacheApi = BaseSpec.getPrebidCacheApi(
-            prebidCacheConfig.getBaseAerospikeConfig("true") +
-                    prebidCacheConfig.getAerospikePreventUuidDuplicationConfig("false")
+            prebidCacheConfig.getBaseAerospikeConfig(true) +
+                    prebidCacheConfig.getAerospikePreventUuidDuplicationConfig(false)
         )
 
         // and: First request object
@@ -126,10 +126,10 @@ class AerospikeCacheSpec : ShouldSpec({
         val xmlPayloadTransfer = PayloadTransfer.getDefaultXmlPayloadTransfer().apply { key = uuid }
         val requestObject = RequestObject.of(xmlPayloadTransfer)
 
-        // and: First request object is saved to Aerospike cache
+        // and: The first request object is saved to Aerospike cache
         prebidCacheApi.postCache(requestObject)
 
-        // and: Second request object with already existing UUID is prepared
+        // and: A second request object with already existing UUID is prepared
         val jsonPayloadTransfer = PayloadTransfer.getDefaultJsonPayloadTransfer().apply { key = uuid }
         val secondRequestObject = RequestObject.of(jsonPayloadTransfer)
         val requestTransferValue = objectMapper.readValue(secondRequestObject.puts[0].value, TransferValue::class.java)
diff --git a/src/test/kotlin/org/prebid/cache/functional/ApacheIgniteCacheSpec.kt b/src/test/kotlin/org/prebid/cache/functional/ApacheIgniteCacheSpec.kt
index fc59dd5..1098b8e 100644
--- a/src/test/kotlin/org/prebid/cache/functional/ApacheIgniteCacheSpec.kt
+++ b/src/test/kotlin/org/prebid/cache/functional/ApacheIgniteCacheSpec.kt
@@ -26,7 +26,7 @@ class ApacheIgniteCacheSpec : ShouldSpec({
 
     should("throw an exception when cache record is absent in Apache ignite repository") {
         // given: Prebid cache config
-        val config = prebidCacheConfig.getBaseApacheIgniteConfig("true")
+        val config = prebidCacheConfig.getBaseApacheIgniteConfig(true)
         val cachePrefix = config["cache.prefix"]
 
         // when: GET cache endpoint with random UUID is called
@@ -42,7 +42,7 @@ class ApacheIgniteCacheSpec : ShouldSpec({
 
     should("throw an exception when cache is absent in Apache ignite repository due to expiration") {
         // given: Prebid Cache with set min and max expiry as 0
-        val config = prebidCacheConfig.getBaseApacheIgniteConfig("false") +
+        val config = prebidCacheConfig.getBaseApacheIgniteConfig(false) +
                 prebidCacheConfig.getCacheExpiryConfig("0", "0")
         val prebidCacheApi = BaseSpec.getPrebidCacheApi(config)
 
@@ -65,7 +65,7 @@ class ApacheIgniteCacheSpec : ShouldSpec({
 
     should("rethrow an exception from Apache ignite cache server when such happens") {
         // given: Prebid Cache with not matched to Apache ignite server namespace
-        val config = prebidCacheConfig.getBaseApacheIgniteConfig("true", getRandomString())
+        val config = prebidCacheConfig.getBaseApacheIgniteConfig(true, getRandomString())
         val prebidCacheApi = BaseSpec.getPrebidCacheApi(config)
 
         // and: Default request object
@@ -86,7 +86,7 @@ class ApacheIgniteCacheSpec : ShouldSpec({
 
     should("return back PBC UUID when allow_external_UUID=false and request object was successfully cached in Apache ignite") {
         // given: Prebid Cache with disabled allow_external_UUID property
-        val prebidCacheApi = BaseSpec.getPrebidCacheApi(prebidCacheConfig.getBaseApacheIgniteConfig("false"))
+        val prebidCacheApi = BaseSpec.getPrebidCacheApi(prebidCacheConfig.getBaseApacheIgniteConfig(false))
 
         // and: Request object
         val requestObject = RequestObject.getDefaultJsonRequestObject()
@@ -102,7 +102,7 @@ class ApacheIgniteCacheSpec : ShouldSpec({
 
     should("return back two random UUIDs when allow_external_UUID=false and 2 payload transfers were successfully cached") {
         // given: Prebid Cache with disabled allow_external_UUID property
-        val prebidCacheApi = BaseSpec.getPrebidCacheApi(prebidCacheConfig.getBaseApacheIgniteConfig("false"))
+        val prebidCacheApi = BaseSpec.getPrebidCacheApi(prebidCacheConfig.getBaseApacheIgniteConfig(false))
 
         // given: Request object
         val xmlPayloadTransfer = PayloadTransfer.getDefaultXmlPayloadTransfer().apply { key = null }
@@ -121,7 +121,7 @@ class ApacheIgniteCacheSpec : ShouldSpec({
 
     should("return back a request UUID when allow_external_UUID=true and request object was successfully cached") {
         // given: Prebid Cache with enabled allow_external_UUID property
-        val prebidCacheApi = BaseSpec.getPrebidCacheApi(prebidCacheConfig.getBaseApacheIgniteConfig("true"))
+        val prebidCacheApi = BaseSpec.getPrebidCacheApi(prebidCacheConfig.getBaseApacheIgniteConfig(true))
 
         // and: Request object with set payload transfer UUID key
         val requestObject = RequestObject.getDefaultJsonRequestObject().apply { puts[0].key = getRandomUuid() }
@@ -137,7 +137,7 @@ class ApacheIgniteCacheSpec : ShouldSpec({
 
     should("return back two request UUIDs when allow_external_UUID=true and 2 payload transfers were successfully cached in Apache ignite") {
         // given: Prebid Cache with allow_external_UUID=true property
-        val prebidCacheApi = BaseSpec.getPrebidCacheApi(prebidCacheConfig.getBaseApacheIgniteConfig("true"))
+        val prebidCacheApi = BaseSpec.getPrebidCacheApi(prebidCacheConfig.getBaseApacheIgniteConfig(true))
 
         // and: Request object with 2 payload transfers and set UUIDs is prepared
         val xmlPayloadTransfer = PayloadTransfer.getDefaultXmlPayloadTransfer().apply { key = getRandomUuid() }
@@ -158,7 +158,7 @@ class ApacheIgniteCacheSpec : ShouldSpec({
 
     should("shouldn't update existing cache record when request with already existing UUID is send") {
         // given: Prebid Cache with allow_external_UUID=true property
-        val prebidCacheApi = BaseSpec.getPrebidCacheApi(prebidCacheConfig.getBaseApacheIgniteConfig("true"))
+        val prebidCacheApi = BaseSpec.getPrebidCacheApi(prebidCacheConfig.getBaseApacheIgniteConfig(true))
 
         // and: First request object
         val uuid = getRandomUuid()
@@ -166,10 +166,10 @@ class ApacheIgniteCacheSpec : ShouldSpec({
         val requestObject = RequestObject.of(xmlPayloadTransfer)
         val requestTransferValue = objectMapper.readValue(requestObject.puts[0].value, TransferValue::class.java)
 
-        // and: First request object is saved to Aerospike cache
+        // and: The first request object is saved to Apache ignite cache
         prebidCacheApi.postCache(requestObject)
 
-        // and: Second request object with already existing UUID is prepared
+        // and: A second request object with already existing UUID is prepared
         val jsonPayloadTransfer = PayloadTransfer.getDefaultJsonPayloadTransfer().apply { key = uuid }
         val secondRequestObject = RequestObject.of(jsonPayloadTransfer)
 
@@ -181,7 +181,7 @@ class ApacheIgniteCacheSpec : ShouldSpec({
         responseObject.responses.size shouldBe 1
         responseObject.responses[0].uuid shouldBe secondRequestObject.puts[0].key
 
-        // and: Cache record was updated in Aerospike with a second request object payload
+        // and: Cache record was updated in Apache ignite with a second request object payload
         val getCacheResponse = prebidCacheApi.getCache(responseObject.responses[0].uuid)
         val responseTransferValue = objectMapper.readValue(getCacheResponse.bodyAsText(), TransferValue::class.java)
 
diff --git a/src/test/kotlin/org/prebid/cache/functional/AuthenticationCacheSpec.kt b/src/test/kotlin/org/prebid/cache/functional/AuthenticationCacheSpec.kt
new file mode 100644
index 0000000..2149be3
--- /dev/null
+++ b/src/test/kotlin/org/prebid/cache/functional/AuthenticationCacheSpec.kt
@@ -0,0 +1,184 @@
+package org.prebid.cache.functional
+
+import io.kotest.assertions.assertSoftly
+import io.kotest.assertions.throwables.shouldThrowExactly
+import io.kotest.core.spec.style.ShouldSpec
+import io.kotest.matchers.shouldBe
+import io.kotest.matchers.string.beEmpty
+import io.ktor.client.statement.bodyAsText
+import io.ktor.http.contentType
+import org.prebid.cache.functional.BaseSpec.Companion.prebidCacheConfig
+import org.prebid.cache.functional.mapper.objectMapper
+import org.prebid.cache.functional.model.request.RequestObject
+import org.prebid.cache.functional.model.request.TransferValue
+import org.prebid.cache.functional.service.ApiException
+import org.prebid.cache.functional.util.getRandomString
+import org.springframework.http.HttpStatus.UNAUTHORIZED
+
+class AuthenticationCacheSpec : ShouldSpec({
+
+    should("should save JSON transfer value without api-key in header when cache-write-secured is disabled") {
+        // given: Prebid Cache with api.cache-write-secured=false property
+        val prebidCacheApi = BaseSpec.getPrebidCacheApi(
+            prebidCacheConfig.getBaseRedisConfig(
+                allowExternalUuid = true,
+                cacheWriteSecured = false,
+                apiKey = getRandomString()
+            )
+        )
+
+        // and: Request object with JSON transfer value
+        val requestObject = RequestObject.getDefaultJsonRequestObject()
+        val requestTransferValue = objectMapper.readValue(requestObject.puts[0].value, TransferValue::class.java)
+
+        // when: POST cache endpoint is called
+        val postResponse = prebidCacheApi.postCache(requestObject, apiKey = null)
+
+        // when: GET cache endpoint is called
+        val getCacheResponse = BaseSpec.getPrebidCacheApi().getCache(postResponse.responses[0].uuid)
+
+        // then: response content type is the same as request object type
+        getCacheResponse.contentType()?.contentType shouldBe "application"
+        getCacheResponse.contentType()?.contentSubtype shouldBe requestObject.puts[0].type.getValue()
+
+        // and: transfer value is returned
+        val responseTransferValue = objectMapper.readValue(getCacheResponse.bodyAsText(), TransferValue::class.java)
+
+        assertSoftly {
+            responseTransferValue.adm shouldBe requestTransferValue.adm
+            responseTransferValue.width shouldBe requestTransferValue.width
+            responseTransferValue.height shouldBe requestTransferValue.height
+        }
+    }
+
+    should("should save JSON transfer value with proper api-key in header when cache-write-secured is enabled") {
+        // given: Prebid Cache with api.cache-write-secured=true property
+        val prebidApiKey = getRandomString()
+        val prebidCacheApi = BaseSpec.getPrebidCacheApi(
+            prebidCacheConfig.getBaseRedisConfig(
+                allowExternalUuid = true,
+                cacheWriteSecured = true,
+                apiKey = prebidApiKey
+            )
+        )
+
+        // and: Request object with JSON transfer value
+        val requestObject = RequestObject.getDefaultJsonRequestObject()
+        val requestTransferValue = objectMapper.readValue(requestObject.puts[0].value, TransferValue::class.java)
+
+        // when: POST cache endpoint is called
+        val postResponse = prebidCacheApi.postCache(requestObject, apiKey = prebidApiKey)
+
+        // when: GET cache endpoint is called
+        val getCacheResponse = BaseSpec.getPrebidCacheApi().getCache(postResponse.responses[0].uuid)
+
+        // then: response content type is the same as request object type
+        getCacheResponse.contentType()?.contentType shouldBe "application"
+        getCacheResponse.contentType()?.contentSubtype shouldBe requestObject.puts[0].type.getValue()
+
+        // and: transfer value is returned
+        val responseTransferValue = objectMapper.readValue(getCacheResponse.bodyAsText(), TransferValue::class.java)
+
+        assertSoftly {
+            responseTransferValue.adm shouldBe requestTransferValue.adm
+            responseTransferValue.width shouldBe requestTransferValue.width
+            responseTransferValue.height shouldBe requestTransferValue.height
+        }
+    }
+
+    should("should throw exception when cache-write-secured is enabled and trying to save payload transfer without api-key") {
+        // given: Prebid Cache with api.cache-write-secured=true property
+        val prebidCacheApi = BaseSpec.getPrebidCacheApi(
+            prebidCacheConfig.getBaseRedisConfig(
+                allowExternalUuid = true,
+                cacheWriteSecured = true,
+                apiKey = getRandomString()
+            )
+        )
+
+        // and: Request object with JSON transfer value
+        val requestObject = RequestObject.getDefaultJsonRequestObject()
+
+        // when: POST cache endpoint is called
+        val exception = shouldThrowExactly<ApiException> { prebidCacheApi.postCache(requestObject, apiKey = null) }
+
+        // then: Bad Request exception is thrown
+        assertSoftly {
+            exception.statusCode shouldBe UNAUTHORIZED.value()
+            exception.responseBody should beEmpty()
+        }
+    }
+
+    should("should throw exception when cache-write-secured is enabled and trying to save payload transfer with empty api-key") {
+        // given: Prebid Cache with api.cache-write-secured=true property
+        val prebidCacheApi = BaseSpec.getPrebidCacheApi(
+            prebidCacheConfig.getBaseRedisConfig(
+                allowExternalUuid = true,
+                cacheWriteSecured = true,
+                apiKey = getRandomString()
+            )
+        )
+
+        // and: Request object with JSON transfer value
+        val requestObject = RequestObject.getDefaultJsonRequestObject()
+
+        // when: POST cache endpoint is called
+        val exception =
+            shouldThrowExactly<ApiException> { prebidCacheApi.postCache(requestObject, apiKey = "") }
+
+        // then: Bad Request exception is thrown
+        assertSoftly {
+            exception.statusCode shouldBe UNAUTHORIZED.value()
+            exception.responseBody should beEmpty()
+        }
+    }
+
+    should("should throw exception when cache-write-secured is enabled and trying to save payload transfer with invalid api-key") {
+        // given: Prebid Cache with api.cache-write-secured=true property
+        val prebidCacheApi = BaseSpec.getPrebidCacheApi(
+            prebidCacheConfig.getBaseRedisConfig(
+                allowExternalUuid = true,
+                cacheWriteSecured = true,
+                apiKey = getRandomString()
+            )
+        )
+
+        // and: Request object with JSON transfer value
+        val requestObject = RequestObject.getDefaultJsonRequestObject()
+
+        // when: POST cache endpoint is called
+        val exception =
+            shouldThrowExactly<ApiException> { prebidCacheApi.postCache(requestObject, apiKey = getRandomString()) }
+
+        // then: Bad Request exception is thrown
+        assertSoftly {
+            exception.statusCode shouldBe UNAUTHORIZED.value()
+            exception.responseBody should beEmpty()
+        }
+    }
+
+    should("should throw exception when cache-write-secured is enabled and trying to save payload transfer with different case strategy api-key") {
+        // given: Prebid Cache with api.cache-write-secured=true property
+        val prebidApiKey = getRandomString()
+        val prebidCacheApi = BaseSpec.getPrebidCacheApi(
+            prebidCacheConfig.getBaseRedisConfig(
+                allowExternalUuid = true,
+                cacheWriteSecured = true,
+                apiKey = prebidApiKey
+            )
+        )
+
+        // and: Request object with JSON transfer value
+        val requestObject = RequestObject.getDefaultJsonRequestObject()
+
+        // when: POST cache endpoint is called
+        val exception =
+            shouldThrowExactly<ApiException> { prebidCacheApi.postCache(requestObject, apiKey = prebidApiKey.uppercase()) }
+
+        // then: Bad Request exception is thrown
+        assertSoftly {
+            exception.statusCode shouldBe UNAUTHORIZED.value()
+            exception.responseBody should beEmpty()
+        }
+    }
+})
diff --git a/src/test/kotlin/org/prebid/cache/functional/BaseSpec.kt b/src/test/kotlin/org/prebid/cache/functional/BaseSpec.kt
index 1d3d23c..e6d083f 100644
--- a/src/test/kotlin/org/prebid/cache/functional/BaseSpec.kt
+++ b/src/test/kotlin/org/prebid/cache/functional/BaseSpec.kt
@@ -13,7 +13,7 @@ abstract class BaseSpec {
             ContainerDependencies.apacheIgniteContainer.getContainerHost(),
         )
 
-        fun getPrebidCacheApi(config: Map<String, String> = prebidCacheConfig.getBaseRedisConfig("false")): PrebidCacheApi {
+        fun getPrebidCacheApi(config: Map<String, String> = prebidCacheConfig.getBaseRedisConfig(false)): PrebidCacheApi {
             return ContainerDependencies.prebidCacheContainerPool.getPrebidCacheContainer(config)
                 .let { container -> PrebidCacheApi(container.host, container.getHostPort()) }
         }
diff --git a/src/test/kotlin/org/prebid/cache/functional/GeneralCacheSpec.kt b/src/test/kotlin/org/prebid/cache/functional/GeneralCacheSpec.kt
index 9a99f0b..5f96717 100644
--- a/src/test/kotlin/org/prebid/cache/functional/GeneralCacheSpec.kt
+++ b/src/test/kotlin/org/prebid/cache/functional/GeneralCacheSpec.kt
@@ -8,6 +8,7 @@ import io.kotest.matchers.string.shouldContain
 import io.ktor.client.statement.bodyAsText
 import io.ktor.http.HttpStatusCode
 import io.ktor.http.contentType
+import org.prebid.cache.functional.BaseSpec.Companion.prebidCacheConfig
 import org.prebid.cache.functional.mapper.objectMapper
 import org.prebid.cache.functional.model.request.MediaType.UNSUPPORTED
 import org.prebid.cache.functional.model.request.RequestObject
@@ -48,7 +49,7 @@ class GeneralCacheSpec : ShouldSpec({
 
     should("throw an exception when allow_external_UUID=true and payload transfer key not in UUID format is given") {
         // given: Prebid Cache with allow_external_UUID=true property
-        val prebidCacheApi = BaseSpec.getPrebidCacheApi(BaseSpec.prebidCacheConfig.getBaseRedisConfig("true"))
+        val prebidCacheApi = BaseSpec.getPrebidCacheApi(prebidCacheConfig.getBaseRedisConfig(true))
 
         // and: Request object with set payload transfer key not in UUID format
         val requestObject = RequestObject.getDefaultJsonRequestObject().apply { puts[0].key = getRandomUuid() + "*" }
@@ -65,7 +66,7 @@ class GeneralCacheSpec : ShouldSpec({
 
     should("throw an exception when allow_external_UUID=true and empty payload transfer key is given") {
         // given: Prebid Cache with allow_external_UUID=true property
-        val prebidCacheApi = BaseSpec.getPrebidCacheApi(BaseSpec.prebidCacheConfig.getBaseRedisConfig("true"))
+        val prebidCacheApi = BaseSpec.getPrebidCacheApi(prebidCacheConfig.getBaseRedisConfig(true))
 
         // and: Request object with set empty payload transfer key
         val requestObject = RequestObject.getDefaultJsonRequestObject().apply { puts[0].key = "" }
@@ -99,12 +100,15 @@ class GeneralCacheSpec : ShouldSpec({
     }
 
     should("return the same JSON transfer value which was saved to cache") {
-        // given: Request object with JSON transfer value
+        // given: Prebid Cache with routes.allow_public_write=true property
+        val prebidCacheApi = BaseSpec.getPrebidCacheApi(prebidCacheConfig.getBaseRedisConfig(true))
+
+        // and: Request object with JSON transfer value
         val requestObject = RequestObject.getDefaultJsonRequestObject()
         val requestTransferValue = objectMapper.readValue(requestObject.puts[0].value, TransferValue::class.java)
 
         // and: POST cache endpoint is called
-        val postResponse = BaseSpec.getPrebidCacheApi().postCache(requestObject)
+        val postResponse = prebidCacheApi.postCache(requestObject)
 
         // when: GET cache endpoint is called
         val getCacheResponse = BaseSpec.getPrebidCacheApi().getCache(postResponse.responses[0].uuid)
diff --git a/src/test/kotlin/org/prebid/cache/functional/ProxyCacheHostSpec.kt b/src/test/kotlin/org/prebid/cache/functional/ProxyCacheHostSpec.kt
index 829ce58..e00d176 100644
--- a/src/test/kotlin/org/prebid/cache/functional/ProxyCacheHostSpec.kt
+++ b/src/test/kotlin/org/prebid/cache/functional/ProxyCacheHostSpec.kt
@@ -45,7 +45,7 @@ class ProxyCacheHostSpec : ShouldSpec({
         // and: Prebid Cache with allow_external_UUID=true and configured proxy cache host is started
         proxyCacheHost =
             "${ContainerDependencies.webCacheContainer.getContainerHost()}:${WebCacheContainer.PORT}"
-        specPrebidCacheConfig = BaseSpec.prebidCacheConfig.getBaseRedisConfig("true") +
+        specPrebidCacheConfig = BaseSpec.prebidCacheConfig.getBaseRedisConfig(true) +
                 BaseSpec.prebidCacheConfig.getProxyCacheHostConfig(proxyCacheHost)
         prebidCacheApi = BaseSpec.getPrebidCacheApi(specPrebidCacheConfig)
     }
@@ -61,7 +61,7 @@ class ProxyCacheHostSpec : ShouldSpec({
     should("throw an exception when proxy cache host doesn't exist") {
         // given: Prebid cache config with set up not existing proxy cache host
         val cacheHost = getRandomString()
-        val config = BaseSpec.prebidCacheConfig.getBaseRedisConfig("true") +
+        val config = BaseSpec.prebidCacheConfig.getBaseRedisConfig(true) +
                 BaseSpec.prebidCacheConfig.getProxyCacheHostConfig(cacheHost)
 
         // when: GET cache endpoint is called with provided cache host
diff --git a/src/test/kotlin/org/prebid/cache/functional/RedisCacheSpec.kt b/src/test/kotlin/org/prebid/cache/functional/RedisCacheSpec.kt
index 3feff70..d3d4401 100644
--- a/src/test/kotlin/org/prebid/cache/functional/RedisCacheSpec.kt
+++ b/src/test/kotlin/org/prebid/cache/functional/RedisCacheSpec.kt
@@ -22,7 +22,7 @@ class RedisCacheSpec : ShouldSpec({
 
     should("throw an exception when cache record is absent in Redis repository") {
         // given: Prebid cache config
-        val config = prebidCacheConfig.getBaseRedisConfig("true")
+        val config = prebidCacheConfig.getBaseRedisConfig(true)
         val cachePrefix = config["cache.prefix"]
 
         // when: GET cache endpoint with random UUID is called
@@ -38,7 +38,7 @@ class RedisCacheSpec : ShouldSpec({
 
     should("rethrow an exception from Redis cache server when such happens") {
         // given: Prebid Cache with set min and max expiry as 0
-        val config = prebidCacheConfig.getBaseRedisConfig("false") + prebidCacheConfig.getCacheExpiryConfig("0", "0")
+        val config = prebidCacheConfig.getBaseRedisConfig(false) + prebidCacheConfig.getCacheExpiryConfig("0", "0")
         val prebidCacheApi = BaseSpec.getPrebidCacheApi(config)
 
         // and: Request object
@@ -88,7 +88,7 @@ class RedisCacheSpec : ShouldSpec({
 
     should("return back a request UUID when allow_external_UUID=true and request object was successfully cached") {
         // given: Prebid Cache with enabled allow_external_UUID property
-        val prebidCacheApi = BaseSpec.getPrebidCacheApi(prebidCacheConfig.getBaseRedisConfig("true"))
+        val prebidCacheApi = BaseSpec.getPrebidCacheApi(prebidCacheConfig.getBaseRedisConfig(true))
 
         // and: Request object with set payload transfer UUID key
         val requestObject = RequestObject.getDefaultJsonRequestObject().apply { puts[0].key = getRandomUuid() }
@@ -104,7 +104,7 @@ class RedisCacheSpec : ShouldSpec({
 
     should("return back two request UUIDs when allow_external_UUID=true and 2 payload transfers were successfully cached in Redis") {
         // given: Prebid Cache with enabled allow_external_UUID property
-        val prebidCacheApi = BaseSpec.getPrebidCacheApi(prebidCacheConfig.getBaseRedisConfig("true"))
+        val prebidCacheApi = BaseSpec.getPrebidCacheApi(prebidCacheConfig.getBaseRedisConfig(true))
 
         // and: Request object with set 2 payload transfers
         val xmlPayloadTransfer = PayloadTransfer.getDefaultXmlPayloadTransfer().apply { key = getRandomUuid() }
diff --git a/src/test/kotlin/org/prebid/cache/functional/SecondaryCacheSpec.kt b/src/test/kotlin/org/prebid/cache/functional/SecondaryCacheSpec.kt
index 0f1e30f..a1aa745 100644
--- a/src/test/kotlin/org/prebid/cache/functional/SecondaryCacheSpec.kt
+++ b/src/test/kotlin/org/prebid/cache/functional/SecondaryCacheSpec.kt
@@ -31,7 +31,7 @@ class SecondaryCacheSpec : ShouldSpec({
             "http://${ContainerDependencies.webCacheContainer.getContainerHost()}:${WebCacheContainer.PORT}"
 
         // and: Prebid Cache with allow_external_UUID=true and configured secondary cache is started
-        specPrebidCacheConfig = BaseSpec.prebidCacheConfig.getBaseAerospikeConfig("true") +
+        specPrebidCacheConfig = BaseSpec.prebidCacheConfig.getBaseAerospikeConfig(true) +
                 BaseSpec.prebidCacheConfig.getSecondaryCacheConfig(webCacheContainerUri)
         prebidCacheApi = BaseSpec.getPrebidCacheApi(specPrebidCacheConfig)
     }
diff --git a/src/test/kotlin/org/prebid/cache/functional/service/PrebidCacheApi.kt b/src/test/kotlin/org/prebid/cache/functional/service/PrebidCacheApi.kt
index 59c6390..7cc55ff 100644
--- a/src/test/kotlin/org/prebid/cache/functional/service/PrebidCacheApi.kt
+++ b/src/test/kotlin/org/prebid/cache/functional/service/PrebidCacheApi.kt
@@ -10,6 +10,7 @@ import io.ktor.client.plugins.defaultRequest
 import io.ktor.client.request.get
 import io.ktor.client.request.header
 import io.ktor.client.request.parameter
+import io.ktor.client.request.port
 import io.ktor.client.request.post
 import io.ktor.client.request.setBody
 import io.ktor.client.statement.HttpResponse
@@ -22,13 +23,26 @@ import org.prebid.cache.functional.model.request.PayloadTransfer
 import org.prebid.cache.functional.model.request.RequestObject
 import org.prebid.cache.functional.model.response.ResponseObject
 
-class PrebidCacheApi(prebidCacheHost: String, prebidCachePort: Int) {
+class PrebidCacheApi(
+    private val prebidCacheHost: String,
+    private val prebidCachePort: Int,
+) {
 
     suspend fun getCache(uuid: String?, proxyCacheHost: String? = null): HttpResponse =
-        get(CACHE_ENDPOINT, mapOf(UUID_QUERY_PARAMETER to uuid, PROXY_CACHE_HOST_QUERY_PARAMETER to proxyCacheHost))
+        get(endpoint = CACHE_ENDPOINT,
+            parameters = mapOf(UUID_QUERY_PARAMETER to uuid, PROXY_CACHE_HOST_QUERY_PARAMETER to proxyCacheHost))
 
-    suspend fun postCache(requestObject: RequestObject, secondaryCache: String? = null): ResponseObject =
-        post(CACHE_ENDPOINT, requestObject, mapOf(SECONDARY_CACHE_QUERY_PARAMETER to secondaryCache)).body()
+    suspend fun postCache(
+        requestObject: RequestObject,
+        secondaryCache: String? = null,
+        apiKey: String? = null
+    ): ResponseObject =
+        post(
+            endpoint = CACHE_ENDPOINT,
+            requestObject = requestObject,
+            parameters = mapOf(SECONDARY_CACHE_QUERY_PARAMETER to secondaryCache),
+            headers = mapOf(API_KEY_PARAMETER to apiKey)
+        ).body()
 
     suspend fun getStorageCache(
         payloadTransferKey: String?,
@@ -36,15 +50,15 @@ class PrebidCacheApi(prebidCacheHost: String, prebidCachePort: Int) {
         apiKey: String?
     ): PayloadTransfer =
         get(
-            STORAGE_ENDPOINT,
-            mapOf(KEY_PARAMETER to payloadTransferKey, APPLICATION_PARAMETER to application),
-            mapOf(API_KEY_PARAMETER to apiKey)
+            endpoint = STORAGE_ENDPOINT,
+            parameters = mapOf(KEY_PARAMETER to payloadTransferKey, APPLICATION_PARAMETER to application),
+            headers = mapOf(API_KEY_PARAMETER to apiKey)
         ).body()
 
     suspend fun postStorageCache(requestObject: PayloadTransfer, apiKey: String? = null): Boolean =
         post(
-            STORAGE_ENDPOINT,
-            requestObject,
+            endpoint = STORAGE_ENDPOINT,
+            requestObject = requestObject,
             headers = mapOf(API_KEY_PARAMETER to apiKey)
         ).status == HttpStatusCode.NoContent
 
@@ -52,7 +66,6 @@ class PrebidCacheApi(prebidCacheHost: String, prebidCachePort: Int) {
         expectSuccess = true
         defaultRequest {
             host = prebidCacheHost
-            port = prebidCachePort
             header(ContentType, Json)
         }
         install(ContentNegotiation) {
@@ -69,9 +82,11 @@ class PrebidCacheApi(prebidCacheHost: String, prebidCachePort: Int) {
 
     private suspend fun get(
         endpoint: String,
+        requestPort: Int = prebidCachePort,
         parameters: Map<String, String?> = emptyMap(),
         headers: Map<String, String?> = emptyMap()
     ): HttpResponse = client.get(endpoint) {
+        port = requestPort
         parameters.forEach { (key, value) ->
             value?.let { parameter(key, value) }
         }
@@ -82,11 +97,13 @@ class PrebidCacheApi(prebidCacheHost: String, prebidCachePort: Int) {
 
     private suspend fun post(
         endpoint: String,
+        requestPort: Int = prebidCachePort,
         requestObject: Any,
         parameters: Map<String, String?> = emptyMap(),
         headers: Map<String, String?> = emptyMap()
     ): HttpResponse =
         client.post(endpoint) {
+            port = requestPort
             setBody(requestObject)
             parameters.forEach { (key, value) ->
                 value?.let { parameter(key, value) }
diff --git a/src/test/kotlin/org/prebid/cache/functional/testcontainers/PrebidCacheContainerConfig.kt b/src/test/kotlin/org/prebid/cache/functional/testcontainers/PrebidCacheContainerConfig.kt
index cab0ba8..8a3c79a 100644
--- a/src/test/kotlin/org/prebid/cache/functional/testcontainers/PrebidCacheContainerConfig.kt
+++ b/src/test/kotlin/org/prebid/cache/functional/testcontainers/PrebidCacheContainerConfig.kt
@@ -7,21 +7,36 @@ import org.prebid.cache.functional.testcontainers.container.ApacheIgniteContaine
 import org.prebid.cache.functional.testcontainers.container.RedisContainer
 import org.prebid.cache.functional.testcontainers.container.WebCacheContainer.Companion.WEB_CACHE_PATH
 
-class PrebidCacheContainerConfig(private val redisHost: String,
-                                 private val aerospikeHost: String,
-                                 private val apacheIgniteHost: String) {
-
-    fun getBaseRedisConfig(allowExternalUuid: String): Map<String, String> =
-        getBaseConfig(allowExternalUuid) + getRedisConfig()
+class PrebidCacheContainerConfig(
+    private val redisHost: String,
+    private val aerospikeHost: String,
+    private val apacheIgniteHost: String
+) {
+
+    fun getBaseRedisConfig(
+        allowExternalUuid: Boolean,
+        cacheWriteSecured: Boolean = false,
+        apiKey: String? = null
+    ): Map<String, String> =
+        getBaseConfig(allowExternalUuid, cacheWriteSecured, apiKey) + getRedisConfig()
 
-    fun getBaseAerospikeConfig(allowExternalUuid: String, aerospikeNamespace: String = NAMESPACE): Map<String, String> =
-        getBaseConfig(allowExternalUuid) + getAerospikeConfig(aerospikeNamespace)
+    fun getBaseAerospikeConfig(
+        allowExternalUuid: Boolean,
+        aerospikeNamespace: String = NAMESPACE,
+        cacheWriteSecured: Boolean = false
+    ): Map<String, String> =
+        getBaseConfig(allowExternalUuid, cacheWriteSecured) + getAerospikeConfig(aerospikeNamespace)
 
-    fun getBaseApacheIgniteConfig(allowExternalUuid: String, ingineCacheName: String = CACHE_NAME): Map<String, String> =
-        getBaseConfig(allowExternalUuid) + getApacheIgniteConfig(ingineCacheName)
+    fun getBaseApacheIgniteConfig(
+        allowExternalUuid: Boolean,
+        ingineCacheName: String = CACHE_NAME,
+        cacheWriteSecured: Boolean = false
+    ): Map<String, String> =
+        getBaseConfig(allowExternalUuid, cacheWriteSecured) + getApacheIgniteConfig(ingineCacheName)
 
     fun getBaseModuleStorageConfig(applicationName: String, apiKey: String): Map<String, String> =
-        getBaseConfig("true") + getModuleStorageRedisConfig(apiKey, applicationName) + getRedisConfig()
+        getBaseConfig(allowExternalUuid = true, apiKey = apiKey) +
+                getModuleStorageRedisConfig(applicationName) + getRedisConfig()
 
     fun getCacheExpiryConfig(minExpiry: String = "15", maxExpiry: String = "28800"): Map<String, String> =
         mapOf(
@@ -33,8 +48,8 @@ class PrebidCacheContainerConfig(private val redisHost: String,
     fun getCacheTimeoutConfig(timeoutMs: String): Map<String, String> =
         mapOf("cache.timeout.ms" to timeoutMs)
 
-    fun getAerospikePreventUuidDuplicationConfig(preventUuidDuplication: String): Map<String, String> =
-        mapOf("spring.aerospike.prevent-u-u-i-d-duplication" to preventUuidDuplication)
+    fun getAerospikePreventUuidDuplicationConfig(preventUuidDuplication: Boolean): Map<String, String> =
+        mapOf("spring.aerospike.prevent-u-u-i-d-duplication" to preventUuidDuplication.toString())
 
     fun getSecondaryCacheConfig(secondaryCacheUri: String): Map<String, String> =
         mapOf(
@@ -75,26 +90,40 @@ class PrebidCacheContainerConfig(private val redisHost: String,
         )
 
     private fun getModuleStorageRedisConfig(
-        apiKey: String,
         applicationName: String,
         timeoutMs: Long = 9999L,
-        endpoint: String = "/storage"
     ): Map<String, String> =
         mapOf(
-            "api.api-key" to apiKey,
-            "api.storage-path" to endpoint,
             "storage.redis.${applicationName}.port" to RedisContainer.PORT.toString(),
             "storage.redis.${applicationName}.host" to redisHost,
             "storage.redis.${applicationName}.timeout" to timeoutMs.toString(),
             "storage.default-ttl-seconds" to 1000L.toString()
         )
 
-    private fun getBaseConfig(allowExternalUuid: String): Map<String, String> =
-        getCachePrefixConfig() + getCacheExpiryConfig() + getAllowExternalUuidConfig(allowExternalUuid) +
-                getCacheTimeoutConfig("2500")
+    private fun getBaseConfig(
+        allowExternalUuid: Boolean,
+        cacheWriteSecured: Boolean = false,
+        apiKey: String? = null,
+        endpoint: String = "/storage"
+    ): Map<String, String> =
+        getCachePrefixConfig() +
+                getCacheExpiryConfig() +
+                getAllowExternalUuidConfig(allowExternalUuid) +
+                getCacheTimeoutConfig("2500") +
+                getApiConfig(endpoint, apiKey, cacheWriteSecured)
 
     private fun getCachePrefixConfig(): Map<String, String> = mapOf("cache.prefix" to "prebid_")
 
-    private fun getAllowExternalUuidConfig(allowExternalUuid: String): Map<String, String> =
-        mapOf("cache.allow-external-u-u-i-d" to allowExternalUuid)
+    private fun getAllowExternalUuidConfig(allowExternalUuid: Boolean): Map<String, String> =
+        mapOf("cache.allow-external-u-u-i-d" to allowExternalUuid.toString())
+
+    private fun getApiConfig(
+        endpoint: String,
+        apiKey: String? = null,
+        allowPublicWrite: Boolean? = null
+    ): Map<String, String> = buildMap {
+        put("api.storage-path", endpoint)
+        apiKey?.let { put("api.api-key", it) }
+        allowPublicWrite?.let { put("api.cache-write-secured", it.toString()) }
+    }
 }
