diff --git a/keyserver/Dockerfile b/keyserver/Dockerfile
--- a/keyserver/Dockerfile
+++ b/keyserver/Dockerfile
@@ -138,6 +138,9 @@
 # Copy protobuf files as a dependency for the shared client libraries
 COPY --chown=comm shared/protos shared/protos/
 
+# Copy identity service gRPC client
+COPY --chown=comm shared/grpc_clients shared/grpc_clients/
+
 # Copy in files needed for patch-package
 COPY --chown=comm patches patches/
 
diff --git a/keyserver/addons/rust-node-addon/Cargo.lock b/keyserver/addons/rust-node-addon/Cargo.lock
--- a/keyserver/addons/rust-node-addon/Cargo.lock
+++ b/keyserver/addons/rust-node-addon/Cargo.lock
@@ -181,7 +181,7 @@
 dependencies = [
  "argon2",
  "log",
- "opaque-ke 2.0.0",
+ "opaque-ke",
  "rand",
  "tonic",
  "wasm-bindgen",
@@ -194,10 +194,10 @@
 checksum = "520fbf3c07483f94e3e3ca9d0cfd913d7718ef2483d2cfd91c0d9e91474ab913"
 
 [[package]]
-name = "constant_time_eq"
-version = "0.1.5"
+name = "convert_case"
+version = "0.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc"
+checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e"
 
 [[package]]
 name = "convert_case"
@@ -224,7 +224,7 @@
 checksum = "ef2b4b23cddf68b89b8f8069890e8c270d54e2d5fe1b143820234805e4cb17ef"
 dependencies = [
  "generic-array",
- "rand_core 0.6.4",
+ "rand_core",
  "subtle",
  "zeroize",
 ]
@@ -239,16 +239,6 @@
  "typenum",
 ]
 
-[[package]]
-name = "crypto-mac"
-version = "0.11.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b1d1a86f49236c215f271d40892d5fc950490551400b02ef360692c29815c714"
-dependencies = [
- "generic-array",
- "subtle",
-]
-
 [[package]]
 name = "ctor"
 version = "0.1.26"
@@ -259,19 +249,6 @@
  "syn 1.0.107",
 ]
 
-[[package]]
-name = "curve25519-dalek"
-version = "3.2.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0b9fdf9972b2bd6af2d913799d9ebc165ea4d2e65878e329d9c6b372c4491b61"
-dependencies = [
- "byteorder",
- "digest 0.9.0",
- "rand_core 0.5.1",
- "subtle",
- "zeroize",
-]
-
 [[package]]
 name = "curve25519-dalek"
 version = "4.0.0-pre.1"
@@ -280,7 +257,7 @@
 dependencies = [
  "byteorder",
  "digest 0.9.0",
- "rand_core 0.6.4",
+ "rand_core",
  "subtle",
  "zeroize",
 ]
@@ -305,6 +282,19 @@
  "syn 1.0.107",
 ]
 
+[[package]]
+name = "derive_more"
+version = "0.99.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321"
+dependencies = [
+ "convert_case 0.4.0",
+ "proc-macro2",
+ "quote",
+ "rustc_version",
+ "syn 1.0.107",
+]
+
 [[package]]
 name = "digest"
 version = "0.9.0"
@@ -355,7 +345,7 @@
  "ff",
  "generic-array",
  "group",
- "rand_core 0.6.4",
+ "rand_core",
  "sec1",
  "subtle",
  "zeroize",
@@ -376,7 +366,7 @@
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d013fc25338cc558c5c2cfbad646908fb23591e2404481826742b651c9af7160"
 dependencies = [
- "rand_core 0.6.4",
+ "rand_core",
  "subtle",
 ]
 
@@ -460,10 +450,22 @@
 checksum = "5dfbfb3a6cfbd390d5c9564ab283a0349b9b9fcd46a706c1eb10e0db70bfbac7"
 dependencies = [
  "ff",
- "rand_core 0.6.4",
+ "rand_core",
  "subtle",
 ]
 
+[[package]]
+name = "grpc_clients"
+version = "0.1.0"
+dependencies = [
+ "derive_more",
+ "prost",
+ "tonic",
+ "tonic-build",
+ "tracing",
+ "tracing-subscriber",
+]
+
 [[package]]
 name = "h2"
 version = "0.3.17"
@@ -504,33 +506,13 @@
  "libc",
 ]
 
-[[package]]
-name = "hkdf"
-version = "0.11.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "01706d578d5c281058480e673ae4086a9f4710d8df1ad80a5b03e39ece5f886b"
-dependencies = [
- "digest 0.9.0",
- "hmac 0.11.0",
-]
-
 [[package]]
 name = "hkdf"
 version = "0.12.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "791a029f6b9fc27657f6f188ec6e5e43f6911f6f878e0dc5501396e09809d437"
 dependencies = [
- "hmac 0.12.1",
-]
-
-[[package]]
-name = "hmac"
-version = "0.11.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2a2a2320eb7ec0ebe8da8f744d7812d9fc4cb4d09344ac01898dbcb6a20ae69b"
-dependencies = [
- "crypto-mac",
- "digest 0.9.0",
+ "hmac",
 ]
 
 [[package]]
@@ -757,7 +739,7 @@
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "16106f0257fa12e364173e5a93e6b9f5bd8ba95b503a3ba58d961a4d60ccb53e"
 dependencies = [
- "convert_case",
+ "convert_case 0.6.0",
  "napi-derive-backend",
  "proc-macro2",
  "quote",
@@ -770,7 +752,7 @@
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "4930d5fa70f5663b9e7d6b4f0816b70d095574ee7f3c865fdb8c43b0f7e6406d"
 dependencies = [
- "convert_case",
+ "convert_case 0.6.0",
  "proc-macro2",
  "quote",
  "syn 1.0.107",
@@ -811,24 +793,6 @@
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3"
 
-[[package]]
-name = "opaque-ke"
-version = "1.2.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f25e5f1be61b7a94f388368a24739318fe4edd2b841d20d7077a422a5391e22f"
-dependencies = [
- "constant_time_eq",
- "curve25519-dalek 3.2.0",
- "digest 0.9.0",
- "displaydoc",
- "generic-array",
- "hkdf 0.11.0",
- "hmac 0.11.0",
- "rand",
- "subtle",
- "zeroize",
-]
-
 [[package]]
 name = "opaque-ke"
 version = "2.0.0"
@@ -836,14 +800,14 @@
 checksum = "76d410412d23781909d90c3900c5783e830586765f2277bccc78167da8af81a5"
 dependencies = [
  "argon2",
- "curve25519-dalek 4.0.0-pre.1",
+ "curve25519-dalek",
  "derive-where",
  "digest 0.10.6",
  "displaydoc",
  "elliptic-curve",
  "generic-array",
- "hkdf 0.12.3",
- "hmac 0.12.1",
+ "hkdf",
+ "hmac",
  "rand",
  "serde",
  "subtle",
@@ -864,7 +828,7 @@
 checksum = "7676374caaee8a325c9e7a2ae557f216c5563a171d6997b0ef8a65af35147700"
 dependencies = [
  "base64ct",
- "rand_core 0.6.4",
+ "rand_core",
  "subtle",
 ]
 
@@ -1012,7 +976,7 @@
 dependencies = [
  "libc",
  "rand_chacha",
- "rand_core 0.6.4",
+ "rand_core",
 ]
 
 [[package]]
@@ -1022,15 +986,9 @@
 checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
 dependencies = [
  "ppv-lite86",
- "rand_core 0.6.4",
+ "rand_core",
 ]
 
-[[package]]
-name = "rand_core"
-version = "0.5.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
-
 [[package]]
 name = "rand_core"
 version = "0.6.4"
@@ -1102,23 +1060,29 @@
 version = "0.1.0"
 dependencies = [
  "comm-opaque2",
+ "grpc_clients",
  "lazy_static",
  "napi",
  "napi-build",
  "napi-derive",
- "opaque-ke 1.2.0",
- "prost",
- "rand",
  "serde",
  "serde_json",
  "tokio",
  "tokio-stream",
  "tonic",
- "tonic-build",
  "tracing",
  "tracing-subscriber",
 ]
 
+[[package]]
+name = "rustc_version"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366"
+dependencies = [
+ "semver",
+]
+
 [[package]]
 name = "rustls"
 version = "0.21.2"
@@ -1185,6 +1149,12 @@
  "zeroize",
 ]
 
+[[package]]
+name = "semver"
+version = "1.0.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918"
+
 [[package]]
 name = "serde"
 version = "1.0.152"
@@ -1603,13 +1573,13 @@
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "081acbe8fcf05d5e8e2aad8ef3d40e02eddeaec07c75a9770d862a0fc0874322"
 dependencies = [
- "curve25519-dalek 4.0.0-pre.1",
+ "curve25519-dalek",
  "derive-where",
  "digest 0.10.6",
  "displaydoc",
  "elliptic-curve",
  "generic-array",
- "rand_core 0.6.4",
+ "rand_core",
  "serde",
  "sha2",
  "subtle",
diff --git a/keyserver/addons/rust-node-addon/Cargo.toml b/keyserver/addons/rust-node-addon/Cargo.toml
--- a/keyserver/addons/rust-node-addon/Cargo.toml
+++ b/keyserver/addons/rust-node-addon/Cargo.toml
@@ -14,22 +14,19 @@
   "tokio_rt",
 ] }
 napi-derive = { version = "2.9.1", default-features = false }
-opaque-ke = "1.2"
-rand = "0.8"
 tonic = { version = "0.9.1", features = ["tls"]}
 tokio = { version = "1.0", features = ["macros", "rt-multi-thread"] }
 tokio-stream = "0.1"
 tracing = "0.1"
 tracing-subscriber = { version = "0.3.16", features = ["env-filter"] }
-prost = "0.11"
 comm-opaque2 = {path = "../../../shared/comm-opaque2"}
+grpc_clients = { path = "../../../shared/grpc_clients" }
 lazy_static = "1.4"
 serde_json = "1.0"
 serde = { version = "1.0", features = ["derive"] }
 
 [build-dependencies]
 napi-build = "2.0.1"
-tonic-build = "0.9.1"
 
 [profile.release]
 lto = true
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
@@ -2,14 +2,4 @@
 
 fn main() {
   napi_build::setup();
-  tonic_build::configure()
-    .build_server(false)
-    .compile(
-      &[
-        "../../../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/src/identity_client/add_reserved_usernames.rs b/keyserver/addons/rust-node-addon/src/identity_client/add_reserved_usernames.rs
--- a/keyserver/addons/rust-node-addon/src/identity_client/add_reserved_usernames.rs
+++ b/keyserver/addons/rust-node-addon/src/identity_client/add_reserved_usernames.rs
@@ -7,8 +7,7 @@
   signature: String,
 ) -> Result<()> {
   // Set up the gRPC client that will be used to talk to the Identity service
-  let channel = get_identity_service_channel().await?;
-  let mut identity_client = IdentityClientServiceClient::new(channel);
+  let mut identity_client = get_identity_client_service_channel().await?;
 
   let add_reserved_usernames_request =
     AddReservedUsernamesRequest { message, signature };
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
deleted file mode 100644
--- a/keyserver/addons/rust-node-addon/src/identity_client/auth_client.rs
+++ /dev/null
@@ -1,67 +0,0 @@
-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/login.rs b/keyserver/addons/rust-node-addon/src/identity_client/login.rs
--- a/keyserver/addons/rust-node-addon/src/identity_client/login.rs
+++ b/keyserver/addons/rust-node-addon/src/identity_client/login.rs
@@ -1,7 +1,9 @@
 use super::*;
 
 use comm_opaque2::client::Login;
-use identity_client::{OpaqueLoginFinishRequest, OpaqueLoginStartRequest};
+use grpc_clients::identity::protos::unauthenticated::{
+  OpaqueLoginFinishRequest, OpaqueLoginStartRequest,
+};
 use tracing::debug;
 
 #[napi]
@@ -20,8 +22,7 @@
   debug!("Attempting to login user: {}", username);
 
   // Set up the gRPC client that will be used to talk to the Identity service
-  let channel = get_identity_service_channel().await?;
-  let mut identity_client = IdentityClientServiceClient::new(channel);
+  let mut identity_client = get_identity_client_service_channel().await?;
 
   // Start OPAQUE registration and send initial registration request
   let mut client_login = Login::new();
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,27 +1,23 @@
 pub mod add_reserved_usernames;
-pub mod auth_client;
-pub mod get_inbound_keys_for_user;
 pub mod login;
 pub mod prekey;
 pub mod register_user;
 pub mod remove_reserved_usernames;
-pub mod identity_client {
-  tonic::include_proto!("identity.client");
-}
 
-use identity_client::identity_client_service_client::IdentityClientServiceClient;
-use identity_client::{
-  inbound_keys_for_user_request::Identifier, AddReservedUsernamesRequest,
-  DeviceKeyUpload, DeviceType, IdentityKeyInfo, InboundKeyInfo,
-  InboundKeysForUserRequest, PreKey, RegistrationFinishRequest,
-  RegistrationStartRequest, RemoveReservedUsernameRequest,
+use grpc_clients::identity::authenticated::AuthLayer;
+use grpc_clients::identity::protos::unauthenticated as client_proto;
+use grpc_clients::identity::protos::authenticated::identity_client_service_client::IdentityClientServiceClient as AuthClient;
+use client_proto::identity_client_service_client::IdentityClientServiceClient;
+use client_proto::{
+  AddReservedUsernamesRequest, DeviceKeyUpload, IdentityKeyInfo, PreKey,
+  RegistrationFinishRequest, RegistrationStartRequest, DeviceType,
+  RemoveReservedUsernameRequest, InboundKeyInfo
 };
 use lazy_static::lazy_static;
 use napi::bindgen_prelude::*;
 use serde::{Deserialize, Serialize};
+use tonic::codegen::InterceptedService;
 use std::env::var;
-use std::path::Path;
-use tonic::transport::{Certificate, ClientTlsConfig};
 use tonic::{transport::Channel, Request};
 use tracing::{self, info, instrument, warn, Level};
 use tracing_subscriber::EnvFilter;
@@ -46,23 +42,6 @@
   };
 }
 
-const CERT_PATHS: &'static [&'static str] = &[
-  // MacOS and newer Ubuntu
-  "/etc/ssl/cert.pem",
-  // Common CA cert paths
-  "/etc/ssl/certs/ca-bundle.crt",
-  "/etc/ssl/certs/ca-certificates.crt",
-];
-
-pub fn get_ca_cert_contents() -> Option<String> {
-  CERT_PATHS
-    .iter()
-    .map(Path::new)
-    .filter(|p| p.exists())
-    .filter_map(|f| std::fs::read_to_string(f).ok())
-    .next()
-}
-
 #[derive(Serialize, Deserialize)]
 #[serde(rename_all = "camelCase")]
 struct IdentityServiceConfig {
@@ -78,29 +57,37 @@
   }
 }
 
-async fn get_identity_service_channel() -> Result<Channel> {
-  let ca_cert = get_ca_cert_contents().expect("Unable to get CA bundle");
-
+async fn get_identity_client_service_channel(
+) -> Result<IdentityClientServiceClient<Channel>> {
   info!("Connecting to identity service");
 
-  let mut channel =
-    Channel::from_static(&IDENTITY_SERVICE_CONFIG.identity_socket_addr);
-
-  // tls_config will fail if the underlying URI is only http://
-  if IDENTITY_SERVICE_CONFIG
-    .identity_socket_addr
-    .starts_with("https:")
-  {
-    channel = channel
-      .tls_config(
-        ClientTlsConfig::new().ca_certificate(Certificate::from_pem(&ca_cert)),
-      )
-      .map_err(|_| {
-        Error::new(Status::GenericFailure, "TLS configure failed")
-      })?;
-  }
+  grpc_clients::identity::get_unauthenticated_client(
+    &IDENTITY_SERVICE_CONFIG.identity_socket_addr,
+  )
+  .await
+  .map_err(|_| {
+    Error::new(
+      Status::GenericFailure,
+      "Unable to connect to identity service".to_string(),
+    )
+  })
+}
+
+async fn get_identity_authenticated_service_channel(
+  user_id: String,
+  device_id: String,
+  access_token: String,
+) -> Result<AuthClient<InterceptedService<Channel, AuthLayer>>> {
+  info!("Connecting to identity service");
 
-  channel.connect().await.map_err(|_| {
+  grpc_clients::identity::get_auth_client(
+    &IDENTITY_SERVICE_CONFIG.identity_socket_addr,
+    user_id,
+    device_id,
+    access_token,
+  )
+  .await
+  .map_err(|_| {
     Error::new(
       Status::GenericFailure,
       "Unable to connect to identity service".to_string(),
diff --git a/keyserver/addons/rust-node-addon/src/identity_client/prekey.rs b/keyserver/addons/rust-node-addon/src/identity_client/prekey.rs
--- a/keyserver/addons/rust-node-addon/src/identity_client/prekey.rs
+++ b/keyserver/addons/rust-node-addon/src/identity_client/prekey.rs
@@ -1,7 +1,8 @@
-use super::auth_client::{
-  auth_proto::RefreshUserPreKeysRequest, client::PreKey, get_auth_client,
-};
+use super::get_identity_authenticated_service_channel;
 use super::{Error, Status};
+use grpc_clients::identity::protos::{
+  authenticated::RefreshUserPreKeysRequest, unauthenticated::PreKey,
+};
 use napi::Result;
 use tracing::warn;
 
@@ -16,7 +17,12 @@
 ) -> 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 mut client = get_identity_authenticated_service_channel(
+    user_id,
+    device_id,
+    access_token,
+  )
+  .await?;
 
   let message = RefreshUserPreKeysRequest {
     new_content_pre_keys: Some(PreKey {
diff --git a/keyserver/addons/rust-node-addon/src/identity_client/register_user.rs b/keyserver/addons/rust-node-addon/src/identity_client/register_user.rs
--- a/keyserver/addons/rust-node-addon/src/identity_client/register_user.rs
+++ b/keyserver/addons/rust-node-addon/src/identity_client/register_user.rs
@@ -18,8 +18,7 @@
   debug!("Attempting to register user: {}", username);
 
   // Set up the gRPC client that will be used to talk to the Identity service
-  let channel = get_identity_service_channel().await?;
-  let mut identity_client = IdentityClientServiceClient::new(channel);
+  let mut identity_client = get_identity_client_service_channel().await?;
 
   // Start OPAQUE registration and send initial registration request
   let mut opaque_registration = comm_opaque2::client::Registration::new();
diff --git a/keyserver/addons/rust-node-addon/src/identity_client/remove_reserved_usernames.rs b/keyserver/addons/rust-node-addon/src/identity_client/remove_reserved_usernames.rs
--- a/keyserver/addons/rust-node-addon/src/identity_client/remove_reserved_usernames.rs
+++ b/keyserver/addons/rust-node-addon/src/identity_client/remove_reserved_usernames.rs
@@ -7,8 +7,7 @@
   signature: String,
 ) -> Result<()> {
   // Set up the gRPC client that will be used to talk to the Identity service
-  let channel = get_identity_service_channel().await?;
-  let mut identity_client = IdentityClientServiceClient::new(channel);
+  let mut identity_client = get_identity_client_service_channel().await?;
 
   let remove_reserved_username_request =
     RemoveReservedUsernameRequest { message, signature };