diff --git a/docs/source/configuration.rst b/docs/source/configuration.rst
index dce8feab367d241aa4991f2c87769fcd6bf42562..e30e03e3741dccae10a75c7055577b6db5c01893 100644
--- a/docs/source/configuration.rst
+++ b/docs/source/configuration.rst
@@ -52,6 +52,20 @@ The client can also be configured with the number of passes to expect in exchang
 
 The value given here must agree with the value the issuer uses in its configuration or redemption may fail.
 
+allowed-public-keys
+~~~~~~~~~~~~~~~~~~~
+
+Regardless of which redeemer is selected,
+the client must also be configured with the public part of the issuer key pair which it will allow to sign tokens::
+
+  [storageclient.plugins.privatestorageio-zkapauthz-v1]
+  allowed-public-keys = AAAA...,BBBB...,CCCC...
+
+The ``allowed-public-keys`` value is a comma-separated list of encoded public keys.
+When tokens are received from an issuer during redemption,
+these are the only public keys which will satisfy the redeemer and cause the tokens to be made available to the client to be spent.
+Tokens received with any other public key will be sequestered and will *not* be spent until some further action is taken.
+
 Server
 ------
 
diff --git a/src/_zkapauthorizer/storage_common.py b/src/_zkapauthorizer/storage_common.py
index 22fa76676d92cdd657bb995ccf95fa7928af7b03..6dcffc44add5a4de361891d29742d2b32c72e533 100644
--- a/src/_zkapauthorizer/storage_common.py
+++ b/src/_zkapauthorizer/storage_common.py
@@ -136,7 +136,11 @@ def get_configured_allowed_public_keys(node_config):
     """
     Read the set of allowed issuer public keys from the given configuration.
     """
-    return set()
+    section_name = u"storageclient.plugins.privatestorageio-zkapauthz-v1"
+    return set(node_config.get_config(
+        section=section_name,
+        option=u"allowed-public-keys",
+    ).strip().split(","))
 
 
 def required_passes(bytes_per_pass, share_sizes):
diff --git a/src/_zkapauthorizer/tests/strategies.py b/src/_zkapauthorizer/tests/strategies.py
index 941028184cd1128505340d0f2e259b4154b0cb1d..2a8590e02ab8b2fec4a7cd519b7744adad229edd 100644
--- a/src/_zkapauthorizer/tests/strategies.py
+++ b/src/_zkapauthorizer/tests/strategies.py
@@ -230,9 +230,16 @@ def server_configurations(signing_key_path):
     )
 
 
+def dummy_ristretto_keys_sets():
+    """
+    Build small sets of "dummy" Ristretto keys.  See ``dummy_ristretto_keys``.
+    """
+    return sets(dummy_ristretto_keys(), min_size=1, max_size=5)
+
+
 def zkapauthz_configuration(
     extra_configurations,
-    allowed_public_keys=sets(dummy_ristretto_keys(), min_size=1, max_size=5),
+    allowed_public_keys=dummy_ristretto_keys_sets(),
 ):
     """
     Build ZKAPAuthorizer client plugin configuration dictionaries.
@@ -251,6 +258,7 @@ def zkapauthz_configuration(
     def merge(extra_configuration, allowed_public_keys):
         config = {
             u"default-token-count": u"32",
+            u"allowed-public-keys": u",".join(allowed_public_keys),
         }
         config.update(extra_configuration)
         return config
@@ -276,13 +284,17 @@ def client_dummyredeemer_configurations():
     """
     Build DummyRedeemer-using configuration values for the client-side plugin.
     """
-    return zkapauthz_configuration(
-        dummy_ristretto_keys().map(
-        lambda key: {
-            u"redeemer": u"dummy",
-            u"issuer-public-key": key,
-        }),
-    )
+    def share_a_key(allowed_keys):
+        return zkapauthz_configuration(
+            just({
+                u"redeemer": u"dummy",
+                # Pick out one of the allowed public keys so that the dummy
+                # appears to produce usable tokens.
+                u"issuer-public-key": next(iter(allowed_keys)),
+            }),
+            allowed_public_keys=just(allowed_keys),
+        )
+    return dummy_ristretto_keys_sets().flatmap(share_a_key)
 
 
 def token_counts():
diff --git a/src/_zkapauthorizer/tests/test_client_resource.py b/src/_zkapauthorizer/tests/test_client_resource.py
index 79c1cdcd2eec1c0a2905183be9a67631727582f8..a4f20985348bcdb7d0051febec8d5f3e26c50eb4 100644
--- a/src/_zkapauthorizer/tests/test_client_resource.py
+++ b/src/_zkapauthorizer/tests/test_client_resource.py
@@ -164,6 +164,7 @@ from ..storage_common import (
     required_passes,
     get_configured_pass_value,
     get_configured_lease_duration,
+    get_configured_allowed_public_keys,
 )
 
 from .strategies import (
@@ -390,6 +391,30 @@ def add_api_token_to_config(basedir, config, api_auth_token):
     config.write_private_config(b"api_auth_token", api_auth_token)
 
 
+class FromConfigurationTests(TestCase):
+    """
+    Tests for ``from_configuration``.
+    """
+    @given(tahoe_configs())
+    def test_allowed_public_keys(self, get_config):
+        """
+        The controller created by ``from_configuration`` is configured to allow
+        the public keys found in the configuration.
+        """
+        tempdir = self.useFixture(TempDir())
+        config = get_config(tempdir.join(b"tahoe"), b"tub.port")
+        allowed_public_keys = get_configured_allowed_public_keys(config)
+
+        # root_from_config is just an easier way to call from_configuration
+        root = root_from_config(config, datetime.now)
+        self.assertThat(
+            root.controller,
+            MatchesStructure(
+                allowed_public_keys=Equals(allowed_public_keys),
+            ),
+        )
+
+
 class GetTokenCountTests(TestCase):
     """
     Tests for ``get_token_count``.
diff --git a/src/_zkapauthorizer/tests/test_storage_client.py b/src/_zkapauthorizer/tests/test_storage_client.py
index 38a0e80bce19b2f041eaa45ba7425de393391674..c697090885784e259258b873c0ce68e340e190f2 100644
--- a/src/_zkapauthorizer/tests/test_storage_client.py
+++ b/src/_zkapauthorizer/tests/test_storage_client.py
@@ -48,6 +48,8 @@ from hypothesis import (
 )
 from hypothesis.strategies import (
     sampled_from,
+    integers,
+    sets,
 )
 
 from twisted.internet.defer import (
@@ -55,15 +57,10 @@ from twisted.internet.defer import (
     fail,
 )
 
-from .matchers import (
-    even,
-    odd,
-    raises,
+from allmydata.client import (
+    config_from_string,
 )
 
-from .strategies import (
-    pass_counts,
-)
 
 from ..api import (
     MorePassesRequired,
@@ -71,19 +68,136 @@ from ..api import (
 from ..model import (
     NotEnoughTokens,
 )
+from ..storage_common import (
+    get_configured_shares_needed,
+    get_configured_shares_total,
+    get_configured_pass_value,
+    get_configured_allowed_public_keys,
+)
 from .._storage_client import (
     call_with_passes,
 )
+
 from .._storage_server import (
     _ValidationResult,
 )
-
+from .matchers import (
+    even,
+    odd,
+    raises,
+)
+from .strategies import (
+    pass_counts,
+    dummy_ristretto_keys,
+)
 from .storage_common import (
     pass_factory,
     integer_passes,
 )
 
 
+
+class GetConfiguredValueTests(TestCase):
+    """
+    Tests for helpers for reading certain configuration values.
+    """
+    @given(integers(min_value=1, max_value=255))
+    def test_get_configured_shares_needed(self, expected):
+        """
+        ``get_configured_shares_needed`` reads the ``shares.needed`` value from
+        the ``client`` section as an integer.
+        """
+        config = config_from_string(
+            "",
+            "",
+            """\
+[client]
+shares.needed = {}
+shares.happy = 5
+shares.total = 10
+""".format(expected),
+        )
+
+        self.assertThat(
+            get_configured_shares_needed(config),
+            Equals(expected),
+        )
+
+    @given(integers(min_value=1, max_value=255))
+    def test_get_configured_shares_total(self, expected):
+        """
+        ``get_configured_shares_total`` reads the ``shares.total`` value from
+        the ``client`` section as an integer.
+        """
+        config = config_from_string(
+            "",
+            "",
+            """\
+[client]
+shares.needed = 5
+shares.happy = 5
+shares.total = {}
+""".format(expected),
+        )
+
+        self.assertThat(
+            get_configured_shares_total(config),
+            Equals(expected),
+        )
+
+    @given(integers(min_value=1, max_value=10000000))
+    def test_get_configured_pass_value(self, expected):
+        """
+        ``get_configured_pass_value`` reads the ``pass-value`` value from the
+        ``storageclient.plugins.privatestorageio-zkapauthz-v1`` section as an
+        integer.
+        """
+        config = config_from_string(
+            "",
+            "",
+            """\
+[client]
+shares.needed = 3
+shares.happy = 5
+shares.total = 10
+
+[storageclient.plugins.privatestorageio-zkapauthz-v1]
+pass-value={}
+""".format(expected),
+        )
+
+        self.assertThat(
+            get_configured_pass_value(config),
+            Equals(expected),
+        )
+
+    @given(sets(dummy_ristretto_keys(), min_size=1, max_size=10))
+    def test_get_configured_allowed_public_keys(self, expected):
+        """
+        ``get_configured_pass_value`` reads the ``pass-value`` value from the
+        ``storageclient.plugins.privatestorageio-zkapauthz-v1`` section as an
+        integer.
+        """
+        config = config_from_string(
+            "",
+            "",
+            """\
+[client]
+shares.needed = 3
+shares.happy = 5
+shares.total = 10
+
+[storageclient.plugins.privatestorageio-zkapauthz-v1]
+allowed-public-keys = {}
+""".format(",".join(expected)),
+        )
+
+        self.assertThat(
+            get_configured_allowed_public_keys(config),
+            Equals(expected),
+        )
+
+
 class CallWithPassesTests(TestCase):
     """
     Tests for ``call_with_passes``.