diff --git a/native/native_rust_library/src/lib.rs b/native/native_rust_library/src/lib.rs
--- a/native/native_rust_library/src/lib.rs
+++ b/native/native_rust_library/src/lib.rs
@@ -1,15 +1,17 @@
 use crate::ffi::{bool_callback, string_callback, void_callback};
 use comm_opaque2::client::{Login, Registration};
 use comm_opaque2::grpc::opaque_error_to_grpc_status as handle_error;
-use grpc_clients::identity::get_unauthenticated_client;
+use grpc_clients::identity::protos::authenticated::{
+  UpdateUserPasswordFinishRequest, UpdateUserPasswordStartRequest,
+};
 use grpc_clients::identity::protos::client::{
   outbound_keys_for_user_request::Identifier, DeleteUserRequest,
   DeviceKeyUpload, DeviceType, Empty, IdentityKeyInfo,
   OpaqueLoginFinishRequest, OpaqueLoginStartRequest, OutboundKeyInfo,
   OutboundKeysForUserRequest, PreKey, RegistrationFinishRequest,
-  RegistrationStartRequest, UpdateUserPasswordFinishRequest,
-  UpdateUserPasswordStartRequest, WalletLoginRequest,
+  RegistrationStartRequest, WalletLoginRequest,
 };
+use grpc_clients::identity::{get_auth_client, get_unauthenticated_client};
 use lazy_static::lazy_static;
 use serde::Serialize;
 use std::sync::Arc;
@@ -622,12 +624,12 @@
     .map_err(handle_error)?;
   let update_password_start_request = UpdateUserPasswordStartRequest {
     opaque_registration_request,
-    access_token: update_password_info.access_token,
-    user_id: update_password_info.user_id,
-    device_id_key: update_password_info.device_id,
   };
-  let mut identity_client = get_unauthenticated_client(
+  let mut identity_client = get_auth_client(
     "http://127.0.0.1:50054",
+    update_password_info.user_id,
+    update_password_info.device_id,
+    update_password_info.access_token,
     CODE_VERSION,
     DEVICE_TYPE.as_str_name().to_lowercase(),
   )
diff --git a/services/identity/src/client_service.rs b/services/identity/src/client_service.rs
--- a/services/identity/src/client_service.rs
+++ b/services/identity/src/client_service.rs
@@ -22,10 +22,8 @@
   RegistrationFinishRequest, RegistrationFinishResponse,
   RegistrationStartRequest, RegistrationStartResponse,
   RemoveReservedUsernameRequest, ReservedRegistrationStartRequest,
-  ReservedWalletLoginRequest, UpdateUserPasswordFinishRequest,
-  UpdateUserPasswordStartRequest, UpdateUserPasswordStartResponse,
-  VerifyUserAccessTokenRequest, VerifyUserAccessTokenResponse,
-  WalletLoginRequest, WalletLoginResponse,
+  ReservedWalletLoginRequest, VerifyUserAccessTokenRequest,
+  VerifyUserAccessTokenResponse, WalletLoginRequest, WalletLoginResponse,
 };
 use crate::config::CONFIG;
 use crate::database::{
@@ -252,80 +250,6 @@
     }
   }
 
-  async fn update_user_password_start(
-    &self,
-    request: tonic::Request<UpdateUserPasswordStartRequest>,
-  ) -> Result<tonic::Response<UpdateUserPasswordStartResponse>, tonic::Status>
-  {
-    let message = request.into_inner();
-
-    let token_is_valid = self
-      .client
-      .verify_access_token(
-        message.user_id.clone(),
-        message.device_id_key,
-        message.access_token,
-      )
-      .await
-      .map_err(handle_db_error)?;
-
-    if !token_is_valid {
-      return Err(tonic::Status::permission_denied("bad token"));
-    }
-
-    let server_registration = comm_opaque2::server::Registration::new();
-    let server_message = server_registration
-      .start(
-        &CONFIG.server_setup,
-        &message.opaque_registration_request,
-        message.user_id.as_bytes(),
-      )
-      .map_err(protocol_error_to_grpc_status)?;
-
-    let update_state = UpdateState {
-      user_id: message.user_id,
-    };
-    let session_id = self
-      .cache
-      .insert_with_uuid_key(WorkflowInProgress::Update(update_state))
-      .await;
-
-    let response = UpdateUserPasswordStartResponse {
-      session_id,
-      opaque_registration_response: server_message,
-    };
-    Ok(Response::new(response))
-  }
-
-  async fn update_user_password_finish(
-    &self,
-    request: tonic::Request<UpdateUserPasswordFinishRequest>,
-  ) -> Result<tonic::Response<Empty>, tonic::Status> {
-    let message = request.into_inner();
-
-    if let Some(WorkflowInProgress::Update(state)) =
-      self.cache.get(&message.session_id)
-    {
-      self.cache.invalidate(&message.session_id).await;
-
-      let server_registration = comm_opaque2::server::Registration::new();
-      let password_file = server_registration
-        .finish(&message.opaque_registration_upload)
-        .map_err(protocol_error_to_grpc_status)?;
-
-      self
-        .client
-        .update_user_password(state.user_id, password_file)
-        .await
-        .map_err(handle_db_error)?;
-
-      let response = Empty {};
-      Ok(Response::new(response))
-    } else {
-      Err(tonic::Status::not_found("session not found"))
-    }
-  }
-
   async fn login_password_user_start(
     &self,
     request: tonic::Request<OpaqueLoginStartRequest>,
diff --git a/services/identity/src/grpc_services/authenticated.rs b/services/identity/src/grpc_services/authenticated.rs
--- a/services/identity/src/grpc_services/authenticated.rs
+++ b/services/identity/src/grpc_services/authenticated.rs
@@ -1,7 +1,15 @@
+use crate::config::CONFIG;
 use crate::{
-  client_service::handle_db_error, constants::request_metadata,
-  database::DatabaseClient, grpc_services::shared::get_value, token::AuthType,
+  client_service::{
+    handle_db_error, CacheExt, UpdateState, WorkflowInProgress,
+  },
+  constants::request_metadata,
+  database::DatabaseClient,
+  grpc_services::shared::get_value,
+  token::AuthType,
 };
+use comm_opaque2::grpc::protocol_error_to_grpc_status;
+use moka::future::Cache;
 use tonic::{Request, Response, Status};
 
 // This must be named client, because generated code from the authenticated
@@ -25,6 +33,7 @@
 #[derive(derive_more::Constructor)]
 pub struct AuthenticatedService {
   db_client: DatabaseClient,
+  cache: Cache<String, WorkflowInProgress>,
 }
 
 fn get_auth_info(req: &Request<()>) -> Option<(String, String, String)> {
@@ -194,4 +203,65 @@
 
     Ok(Response::new(FindUserIdResponse { user_id }))
   }
+
+  async fn update_user_password_start(
+    &self,
+    request: tonic::Request<auth_proto::UpdateUserPasswordStartRequest>,
+  ) -> Result<
+    tonic::Response<auth_proto::UpdateUserPasswordStartResponse>,
+    tonic::Status,
+  > {
+    let (user_id, _) = get_user_and_device_id(&request)?;
+    let message = request.into_inner();
+
+    let server_registration = comm_opaque2::server::Registration::new();
+    let server_message = server_registration
+      .start(
+        &CONFIG.server_setup,
+        &message.opaque_registration_request,
+        user_id.as_bytes(),
+      )
+      .map_err(protocol_error_to_grpc_status)?;
+
+    let update_state = UpdateState { user_id };
+    let session_id = self
+      .cache
+      .insert_with_uuid_key(WorkflowInProgress::Update(update_state))
+      .await;
+
+    let response = auth_proto::UpdateUserPasswordStartResponse {
+      session_id,
+      opaque_registration_response: server_message,
+    };
+    Ok(Response::new(response))
+  }
+
+  async fn update_user_password_finish(
+    &self,
+    request: tonic::Request<auth_proto::UpdateUserPasswordFinishRequest>,
+  ) -> Result<tonic::Response<Empty>, tonic::Status> {
+    let message = request.into_inner();
+
+    let Some(WorkflowInProgress::Update(state)) =
+      self.cache.get(&message.session_id)
+    else {
+      return Err(tonic::Status::not_found("session not found"));
+    };
+
+    self.cache.invalidate(&message.session_id).await;
+
+    let server_registration = comm_opaque2::server::Registration::new();
+    let password_file = server_registration
+      .finish(&message.opaque_registration_upload)
+      .map_err(protocol_error_to_grpc_status)?;
+
+    self
+      .db_client
+      .update_user_password(state.user_id, password_file)
+      .await
+      .map_err(handle_db_error)?;
+
+    let response = Empty {};
+    Ok(Response::new(response))
+  }
 }
diff --git a/services/identity/src/main.rs b/services/identity/src/main.rs
--- a/services/identity/src/main.rs
+++ b/services/identity/src/main.rs
@@ -80,13 +80,13 @@
         .time_to_live(Duration::from_secs(10))
         .build();
       let inner_client_service =
-        ClientService::new(database_client.clone(), workflow_cache);
+        ClientService::new(database_client.clone(), workflow_cache.clone());
       let client_service = IdentityClientServiceServer::with_interceptor(
         inner_client_service,
         grpc_services::shared::version_interceptor,
       );
       let inner_auth_service =
-        AuthenticatedService::new(database_client.clone());
+        AuthenticatedService::new(database_client.clone(), workflow_cache);
       let auth_service =
         AuthServer::with_interceptor(inner_auth_service, move |req| {
           grpc_services::authenticated::auth_interceptor(req, &database_client)
diff --git a/shared/protos/identity_authenticated.proto b/shared/protos/identity_authenticated.proto
--- a/shared/protos/identity_authenticated.proto
+++ b/shared/protos/identity_authenticated.proto
@@ -19,6 +19,12 @@
   rpc RefreshUserPreKeys(RefreshUserPreKeysRequest)
     returns (identity.client.Empty) {}
 
+  // Called by user to update password and receive new access token
+  rpc UpdateUserPasswordStart(UpdateUserPasswordStartRequest) returns
+    (UpdateUserPasswordStartResponse) {}
+  rpc UpdateUserPasswordFinish(UpdateUserPasswordFinishRequest) returns
+    (identity.client.Empty) {}
+
   // Called by clients to get required keys for opening a connection
   // to a user's keyserver
   rpc GetKeyserverKeys(OutboundKeysForUserRequest) returns
@@ -79,3 +85,27 @@
   // none if user not found
   optional string userID = 1;
 }
+
+// UpdateUserPassword
+
+// Request for updating a user, similar to registration but need a
+// access token to validate user before updating password
+message UpdateUserPasswordStartRequest {
+  // Message sent to initiate PAKE registration (step 1)
+  bytes opaqueRegistrationRequest = 1;
+}
+
+// Do a user registration, but overwrite the existing credentials
+// after validation of user
+message UpdateUserPasswordFinishRequest {
+  // Identifier used to correlate start and finish request
+  string sessionID = 1;
+  // Opaque client registration upload (step 3)
+  bytes opaqueRegistrationUpload = 2;
+}
+
+message UpdateUserPasswordStartResponse {
+  // Identifier used to correlate start request with finish request
+  string sessionID = 1;
+  bytes opaqueRegistrationResponse = 2;
+}
diff --git a/shared/protos/identity_client.proto b/shared/protos/identity_client.proto
--- a/shared/protos/identity_client.proto
+++ b/shared/protos/identity_client.proto
@@ -17,11 +17,6 @@
     returns (RegistrationStartResponse) {}
   rpc RegisterPasswordUserFinish(RegistrationFinishRequest) returns (
     RegistrationFinishResponse) {}
-  // Called by user to update password and receive new access token
-  rpc UpdateUserPasswordStart(UpdateUserPasswordStartRequest) returns
-    (UpdateUserPasswordStartResponse) {}
-  rpc UpdateUserPasswordFinish(UpdateUserPasswordFinishRequest) returns
-    (Empty) {}
   // Called by user to register device and get an access token
   rpc LoginPasswordUserStart(OpaqueLoginStartRequest) returns
     (OpaqueLoginStartResponse) {}
@@ -174,36 +169,6 @@
   string accessToken = 2;
 }
 
-// UpdateUserPassword
-
-// Request for updating a user, similar to registration but need a
-// access token to validate user before updating password
-message UpdateUserPasswordStartRequest {
-  // Message sent to initiate PAKE registration (step 1)
-  bytes opaqueRegistrationRequest = 1;
-  // Used to validate user, before attempting to update password
-  string accessToken = 2;
-  string userID = 3;
-  // Public ed25519 key used for signing. We need this to look up a device's
-  // access token
-  string deviceIDKey = 4;
-}
-
-// Do a user registration, but overwrite the existing credentials
-// after validation of user
-message UpdateUserPasswordFinishRequest {
-  // Identifier used to correlate start and finish request
-  string sessionID = 1;
-  // Opaque client registration upload (step 3)
-  bytes opaqueRegistrationUpload = 2;
-}
-
-message UpdateUserPasswordStartResponse {
-  // Identifier used to correlate start request with finish request
-  string sessionID = 1;
-  bytes opaqueRegistrationResponse = 2;
-}
-
 // LoginUser
 
 message OpaqueLoginStartRequest {