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
@@ -2,12 +2,34 @@
 
 import type { SignedIdentityKeysBlob } from 'lib/types/crypto-types.js';
 
+type UserLoginResponse = {
+  +userID: string,
+  +accessToken: string,
+};
+
 type RustNativeBindingAPI = {
+  +loginUser: (
+    username: string,
+    password: string,
+    signedIdentityKeysBlob: SignedIdentityKeysBlob,
+    contentPrekey: string,
+    contentPrekeySignature: string,
+    notifPrekey: string,
+    notifPrekeySignature: string,
+    contentOneTimeKeys: $ReadOnlyArray<string>,
+    notifOneTimeKeys: $ReadOnlyArray<string>,
+  ) => Promise<UserLoginResponse>,
   +registerUser: (
     username: string,
     password: string,
     signedIdentityKeysBlob: SignedIdentityKeysBlob,
-  ) => Promise<boolean>,
+    contentPrekey: string,
+    contentPrekeySignature: string,
+    notifPrekey: string,
+    notifPrekeySignature: string,
+    contentOneTimeKeys: $ReadOnlyArray<string>,
+    notifOneTimeKeys: $ReadOnlyArray<string>,
+  ) => Promise<UserLoginResponse>,
   +addReservedUsernames: (message: string, signature: string) => Promise<void>,
   +removeReservedUsername: (
     message: string,
diff --git a/keyserver/addons/rust-node-addon/src/identity_client/login.rs b/keyserver/addons/rust-node-addon/src/identity_client/login.rs
new file mode 100644
--- /dev/null
+++ b/keyserver/addons/rust-node-addon/src/identity_client/login.rs
@@ -0,0 +1,77 @@
+use super::*;
+
+use comm_opaque2::client::Login;
+use identity_client::{OpaqueLoginFinishRequest, OpaqueLoginStartRequest};
+
+#[napi]
+#[instrument(skip_all)]
+pub async fn login_user(
+  username: String,
+  password: String,
+  signed_identity_keys_blob: SignedIdentityKeysBlob,
+  content_prekey: String,
+  content_prekey_signature: String,
+  notif_prekey: String,
+  notif_prekey_signature: String,
+  content_one_time_keys: Vec<String>,
+  notif_one_time_keys: Vec<String>,
+) -> Result<UserLoginInfo> {
+  // 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);
+
+  // Start OPAQUE registration and send initial registration request
+  let mut client_login = Login::new();
+  let opaque_login_request = client_login
+    .start(&password)
+    .map_err(|_| Error::from_status(Status::GenericFailure))?;
+
+  let login_start_request = OpaqueLoginStartRequest {
+    opaque_login_request,
+    username: username,
+    device_key_upload: Some(DeviceKeyUpload {
+      device_key_info: Some(IdentityKeyInfo {
+        payload: signed_identity_keys_blob.payload,
+        payload_signature: signed_identity_keys_blob.signature,
+        social_proof: None,
+      }),
+      content_upload: Some(PreKey {
+        pre_key: content_prekey,
+        pre_key_signature: content_prekey_signature,
+      }),
+      notif_upload: Some(PreKey {
+        pre_key: notif_prekey,
+        pre_key_signature: notif_prekey_signature,
+      }),
+      onetime_content_prekeys: content_one_time_keys,
+      onetime_notif_prekeys: notif_one_time_keys,
+    }),
+  };
+
+  let login_start_response = identity_client
+    .login_password_user_start(login_start_request)
+    .await
+    .map_err(|_| Error::from_status(Status::GenericFailure))?
+    .into_inner();
+
+  let opaque_login_upload = client_login
+    .finish(&login_start_response.opaque_login_response)
+    .map_err(|_| Error::from_status(Status::GenericFailure))?;
+  let login_finish_request = OpaqueLoginFinishRequest {
+    session_id: login_start_response.session_id,
+    opaque_login_upload,
+  };
+
+  let login_finish_response = identity_client
+    .login_password_user_finish(login_finish_request)
+    .await
+    .map_err(|_| Error::from_status(Status::GenericFailure))?
+    .into_inner();
+
+  let user_info = UserLoginInfo {
+    user_id: login_finish_response.user_id,
+    access_token: login_finish_response.access_token,
+  };
+
+  Ok(user_info)
+}
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,13 +1,14 @@
 pub mod add_reserved_usernames;
+pub mod login;
 pub mod register_user;
-pub mod remove_reserved_username;
+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::{
-  AddReservedUsernamesRequest, DeviceKeyUpload, IdentityKeyInfo,
+  AddReservedUsernamesRequest, DeviceKeyUpload, IdentityKeyInfo, PreKey,
   RegistrationFinishRequest, RegistrationStartRequest,
   RemoveReservedUsernameRequest,
 };
@@ -15,7 +16,7 @@
 use napi::bindgen_prelude::*;
 use serde::{Deserialize, Serialize};
 use std::env::var;
-use tonic::{metadata::MetadataValue, transport::Channel, Request};
+use tonic::{transport::Channel, Request};
 use tracing::instrument;
 
 lazy_static! {
@@ -62,3 +63,9 @@
   pub payload: String,
   pub signature: String,
 }
+
+#[napi(object)]
+pub struct UserLoginInfo {
+  pub user_id: String,
+  pub access_token: String,
+}
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
@@ -6,20 +6,16 @@
   username: String,
   password: String,
   signed_identity_keys_blob: SignedIdentityKeysBlob,
-) -> Result<bool> {
+  content_prekey: String,
+  content_prekey_signature: String,
+  notif_prekey: String,
+  notif_prekey_signature: String,
+  content_one_time_keys: Vec<String>,
+  notif_one_time_keys: Vec<String>,
+) -> Result<UserLoginInfo> {
   // Set up the gRPC client that will be used to talk to the Identity service
   let channel = get_identity_service_channel().await?;
-  let token: MetadataValue<_> = IDENTITY_SERVICE_CONFIG
-    .identity_auth_token
-    .parse()
-    .map_err(|_| Error::from_status(Status::GenericFailure))?;
-  let mut identity_client = IdentityClientServiceClient::with_interceptor(
-    channel,
-    |mut req: Request<()>| {
-      req.metadata_mut().insert("authorization", token.clone());
-      Ok(req)
-    },
-  );
+  let mut identity_client = IdentityClientServiceClient::new(channel);
 
   // Start OPAQUE registration and send initial registration request
   let mut opaque_registration = comm_opaque2::client::Registration::new();
@@ -32,16 +28,16 @@
       payload_signature: signed_identity_keys_blob.signature,
       social_proof: None,
     }),
-    content_upload: Some(identity_client::PreKey {
-      pre_key: String::new(),
-      pre_key_signature: String::new(),
+    content_upload: Some(PreKey {
+      pre_key: content_prekey,
+      pre_key_signature: content_prekey_signature,
     }),
-    notif_upload: Some(identity_client::PreKey {
-      pre_key: String::new(),
-      pre_key_signature: String::new(),
+    notif_upload: Some(PreKey {
+      pre_key: notif_prekey,
+      pre_key_signature: notif_prekey_signature,
     }),
-    onetime_content_prekeys: Vec::new(),
-    onetime_notif_prekeys: Vec::new(),
+    onetime_content_prekeys: content_one_time_keys,
+    onetime_notif_prekeys: notif_one_time_keys,
   };
   let registration_start_request = Request::new(RegistrationStartRequest {
     opaque_registration_request,
@@ -68,12 +64,16 @@
     opaque_registration_upload,
   });
 
-  identity_client
+  let registration_response = identity_client
     .register_password_user_finish(registration_finish_request)
     .await
     .map_err(|_| Error::from_status(Status::GenericFailure))?
     .into_inner();
 
-  // Keyserver doesn't need the access token, so we just return a bool
-  Ok(true)
+  let user_info = UserLoginInfo {
+    user_id: registration_response.user_id,
+    access_token: registration_response.access_token,
+  };
+
+  Ok(user_info)
 }
diff --git a/keyserver/addons/rust-node-addon/src/identity_client/remove_reserved_username.rs b/keyserver/addons/rust-node-addon/src/identity_client/remove_reserved_usernames.rs
rename from keyserver/addons/rust-node-addon/src/identity_client/remove_reserved_username.rs
rename to keyserver/addons/rust-node-addon/src/identity_client/remove_reserved_usernames.rs