diff --git a/src/components/S3KeyView.vue b/src/components/S3KeyView.vue
new file mode 100644
index 0000000000000000000000000000000000000000..71c865308c06a0bd2b731658fca1023429b3a91c
--- /dev/null
+++ b/src/components/S3KeyView.vue
@@ -0,0 +1,58 @@
+<script setup lang="ts">
+import type { S3Key } from "@/client";
+import type { Ref } from "vue";
+import { ref, watch } from "vue";
+import BootstrapIcon from "@/components/BootstrapIcon.vue";
+
+const props = defineProps<{
+  s3key: S3Key;
+}>();
+
+watch(
+  () => props.s3key.access_key,
+  () => {
+    visibleSecret.value = false;
+  }
+);
+
+const visibleSecret: Ref<boolean> = ref(false);
+</script>
+
+<template>
+  <h3>Access Key:</h3>
+  <input
+    class="form-control-plaintext text-white fs-4"
+    type="text"
+    :value="props.s3key.access_key"
+    aria-label="S3 Access Key"
+    readonly
+  />
+  <div class="d-flex align-items-center">
+    <span class="fs-3">Secret Key:</span>
+    <button
+      class="btn btn-outline-secondary ms-3"
+      :class="{ active: visibleSecret }"
+      data-bs-toggle="button"
+      @click="visibleSecret = !visibleSecret"
+    >
+      <bootstrap-icon
+        :width="18"
+        :height="18"
+        fill="white"
+        :icon="visibleSecret ? 'eye' : 'eye-slash'"
+      />
+    </button>
+  </div>
+  <input
+    id="s3-secret-key"
+    class="form-control-plaintext text-white fs-4 mb-3"
+    :type="visibleSecret ? 'text' : 'password'"
+    :value="props.s3key.secret_key"
+    aria-label="S3 Access Key"
+    aria-describedby="s3-secret-key"
+    readonly
+  />
+  <button type="button" class="btn btn-danger fs-5" disabled>Delete</button>
+</template>
+
+<style scoped></style>
diff --git a/src/router/index.ts b/src/router/index.ts
index 15fec0220530936e07512857ad07be5b8d401f0d..187869497a62e4bb8ff84338708d2beca0711ef2 100644
--- a/src/router/index.ts
+++ b/src/router/index.ts
@@ -26,9 +26,6 @@ const router = createRouter({
         {
           path: "object-storage/s3-keys",
           name: "s3_keys",
-          // route level code-splitting
-          // this generates a separate chunk (About.[hash].js) for this route
-          // which is lazy-loaded when the route is visited.
           component: () => import("../views/object-storage/S3KeysView.vue"),
         },
       ],
diff --git a/src/views/object-storage/S3KeysView.vue b/src/views/object-storage/S3KeysView.vue
index cc1e83c91f73a6afc72aac3d73eb7631aa9b0129..8995f7d4a074ee2769ae519bab8bf4fca75f364a 100644
--- a/src/views/object-storage/S3KeysView.vue
+++ b/src/views/object-storage/S3KeysView.vue
@@ -1,14 +1,75 @@
+<script setup lang="ts">
+import S3KeyView from "@/components/S3KeyView.vue";
+import { reactive, onMounted } from "vue";
+import type { S3Key } from "@/client";
+import { KeyService } from "@/client";
+import { useAuthStore } from "@/stores/auth";
+
+const authStore = useAuthStore();
+
+authStore.$onAction(({ name, args }) => {
+  if (name === "updateUser") {
+    refreshKeys(args[0].uid);
+  }
+});
+
+const keyState = reactive({
+  keys: [],
+  activeKey: 0,
+  loading: true,
+} as {
+  keys: S3Key[];
+  activeKey: number;
+  loading: boolean;
+});
+
+function refreshKeys(uid: string) {
+  KeyService.keyGetUserKeys(uid)
+    .then((keys) => {
+      keyState.keys = keys;
+    })
+    .catch((err) => console.error(err))
+    .finally(() => (keyState.loading = false));
+}
+
+onMounted(() => {
+  if (authStore.user != null) {
+    refreshKeys(authStore.user.uid);
+  }
+});
+</script>
+
 <template>
-  <div class="about">
-    <h1>This is the S3 Key Page</h1>
+  <div class="row m-2 border-bottom border-light mt-4">
+    <div class="col-12"></div>
+    <h1 class="mb-2 text-light">S3 Keys</h1>
+  </div>
+  <div class="row m-2 mt-4">
+    <div class="col-4">
+      <div v-for="(s3key, index) in keyState.keys" :key="s3key.access_key">
+        <button
+          class="btn w-100 fs-5 mb-3"
+          @click="keyState.activeKey = index"
+          :class="{
+            'btn-light': keyState.activeKey !== index,
+            'btn-primary': keyState.activeKey === index,
+          }"
+        >
+          {{ s3key.access_key }}
+        </button>
+      </div>
+    </div>
+    <div class="col-7 offset-md-1">
+      <s3-key-view
+        v-if="keyState.keys.length > 0"
+        :s3key="keyState.keys[keyState.activeKey]"
+      />
+      <div v-else>
+        No keys here. <br />
+        Create a new Key to interact with your Buckets again.
+      </div>
+    </div>
   </div>
 </template>
 
-<style>
-@media (min-width: 1024px) {
-  .about {
-    display: flex;
-    align-items: center;
-  }
-}
-</style>
+<style scoped></style>