diff --git a/keyserver/addons/rust-node-addon/build.rs b/keyserver/addons/rust-node-addon/build.rs
--- a/keyserver/addons/rust-node-addon/build.rs
+++ b/keyserver/addons/rust-node-addon/build.rs
@@ -5,7 +5,10 @@
   tonic_build::configure()
     .build_server(false)
     .compile(
-      &["../../../shared/protos/identity_client.proto"],
+      &[
+        "../../../shared/protos/identity_client.proto",
+        "../../../shared/protos/identity_authenticated.proto",
+      ],
       &["../../../shared/protos"],
     )
     .unwrap_or_else(|e| panic!("Failed to compile protos {:?}", e));
diff --git a/keyserver/addons/rust-node-addon/rust-binding-types.js b/keyserver/addons/rust-node-addon/rust-binding-types.js
--- a/keyserver/addons/rust-node-addon/rust-binding-types.js
+++ b/keyserver/addons/rust-node-addon/rust-binding-types.js
@@ -35,6 +35,15 @@
     message: string,
     signature: string,
   ) => Promise<void>,
+  +publish_prekeys: (
+    userId: string,
+    deviceId: string,
+    accessToken: string,
+    contentPrekey: string,
+    contentPrekeySignature: string,
+    notifPrekey: string,
+    notifPrekeySignature: string,
+  ) => Promise<boolean>,
 };
 
 export type { RustNativeBindingAPI };
diff --git a/keyserver/addons/rust-node-addon/src/identity_client/auth_client.rs b/keyserver/addons/rust-node-addon/src/identity_client/auth_client.rs
new file mode 100644
--- /dev/null
+++ b/keyserver/addons/rust-node-addon/src/identity_client/auth_client.rs
@@ -0,0 +1,67 @@
+pub mod client {
+  tonic::include_proto!("identity.client");
+}
+pub mod auth_proto {
+  tonic::include_proto!("identity.authenticated");
+}
+use auth_proto::identity_client_service_client::IdentityClientServiceClient as AuthClient;
+use tonic::{
+  codegen::InterceptedService,
+  metadata::{errors::InvalidMetadataValue, Ascii, MetadataValue},
+  service::Interceptor,
+  transport::{Channel, Endpoint},
+  Request, Status,
+};
+
+use super::IDENTITY_SERVICE_CONFIG;
+
+pub struct AuthLayer {
+  user_id: String,
+  device_id: String,
+  access_token: String,
+}
+
+trait ToAscii {
+  fn parse_to_ascii(&self) -> Result<MetadataValue<Ascii>, Status>;
+}
+
+impl ToAscii for str {
+  fn parse_to_ascii(&self) -> Result<MetadataValue<Ascii>, Status> {
+    self.parse().map_err(|e: InvalidMetadataValue| {
+      Status::invalid_argument(format!(
+        "Non-Ascii character present in metadata value: {}",
+        e
+      ))
+    })
+  }
+}
+
+impl Interceptor for AuthLayer {
+  fn call(&mut self, mut request: Request<()>) -> Result<Request<()>, Status> {
+    let metadata = request.metadata_mut();
+    metadata.insert("user_id", self.user_id.parse_to_ascii()?);
+    metadata.insert("device_id", self.device_id.parse_to_ascii()?);
+    metadata.insert("access_token", self.access_token.parse_to_ascii()?);
+
+    Ok(request)
+  }
+}
+pub async fn get_auth_client(
+  user_id: String,
+  device_id: String,
+  access_token: String,
+) -> AuthClient<InterceptedService<Channel, AuthLayer>> {
+  let channel =
+    Endpoint::from_static(&IDENTITY_SERVICE_CONFIG.identity_socket_addr)
+      .connect()
+      .await
+      .unwrap();
+
+  let interceptor = AuthLayer {
+    user_id,
+    device_id,
+    access_token,
+  };
+
+  AuthClient::with_interceptor(channel, interceptor)
+}
diff --git a/keyserver/addons/rust-node-addon/src/identity_client/mod.rs b/keyserver/addons/rust-node-addon/src/identity_client/mod.rs
--- a/keyserver/addons/rust-node-addon/src/identity_client/mod.rs
+++ b/keyserver/addons/rust-node-addon/src/identity_client/mod.rs
@@ -1,5 +1,7 @@
 pub mod add_reserved_usernames;
+pub mod auth_client;
 pub mod login;
+pub mod prekey;
 pub mod register_user;
 pub mod remove_reserved_usernames;
 pub mod identity_client {
diff --git a/keyserver/addons/rust-node-addon/src/identity_client/prekey.rs b/keyserver/addons/rust-node-addon/src/identity_client/prekey.rs
new file mode 100644
--- /dev/null
+++ b/keyserver/addons/rust-node-addon/src/identity_client/prekey.rs
@@ -0,0 +1,41 @@
+use super::auth_client::{
+  auth_proto::RefreshUserPreKeysRequest, client::PreKey, get_auth_client,
+};
+use super::{Error, Status};
+use napi::Result;
+use tracing::warn;
+
+pub async fn publish_prekeys(
+  user_id: String,
+  device_id: String,
+  access_token: String,
+  content_prekey: String,
+  content_prekey_signature: String,
+  notif_prekey: String,
+  notif_prekey_signature: String,
+) -> Result<bool> {
+  // Once this rust addon can do getCommConfig, remove explicit passing of user
+  // credentials within this scope
+  let mut client = get_auth_client(user_id, device_id, access_token).await;
+
+  let message = RefreshUserPreKeysRequest {
+    new_content_pre_keys: Some(PreKey {
+      pre_key: content_prekey,
+      pre_key_signature: content_prekey_signature,
+    }),
+    new_notif_pre_keys: Some(PreKey {
+      pre_key: notif_prekey,
+      pre_key_signature: notif_prekey_signature,
+    }),
+  };
+
+  client.refresh_user_pre_keys(message).await.map_err(|e| {
+    warn!(
+      "Failed to upload new prekeys to identity service: {:?}",
+      e.message()
+    );
+    Error::from_status(Status::GenericFailure)
+  })?;
+
+  Ok(true)
+}