diff --git a/lib/types/identity-service-types.js b/lib/types/identity-service-types.js
--- a/lib/types/identity-service-types.js
+++ b/lib/types/identity-service-types.js
@@ -135,6 +135,10 @@
   // updating device list is possible only on Native
   // web cannot be a primary device, so there's no need to expose it to JS
   +updateDeviceList?: (newDeviceList: SignedDeviceList) => Promise<void>;
+  +uploadKeysForRegisteredDeviceAndLogIn: (
+    userID: string,
+    nonceChallengeResponse: SignedMessage,
+  ) => Promise<IdentityAuthResult>;
 }
 
 export type IdentityServiceAuthLayer = {
@@ -184,6 +188,15 @@
 export const signedDeviceListHistoryValidator: TList<Array<SignedDeviceList>> =
   t.list(signedDeviceListValidator);
 
+export type NonceChallenge = {
+  +nonce: string,
+};
+
+export type SignedMessage = {
+  +message: string,
+  +signature: string,
+};
+
 export const ONE_TIME_KEYS_NUMBER = 10;
 
 export const identityDeviceTypes = Object.freeze({
diff --git a/native/identity-service/identity-service-context-provider.react.js b/native/identity-service/identity-service-context-provider.react.js
--- a/native/identity-service/identity-service-context-provider.react.js
+++ b/native/identity-service/identity-service-context-provider.react.js
@@ -12,6 +12,7 @@
 import {
   type SignedDeviceList,
   signedDeviceListHistoryValidator,
+  type SignedMessage,
   type DeviceOlmOutboundKeys,
   deviceOlmOutboundKeysValidator,
   type IdentityServiceClient,
@@ -450,6 +451,50 @@
 
         return validatedResult;
       },
+      uploadKeysForRegisteredDeviceAndLogIn: async (
+        userID: string,
+        nonceChallengeResponse: SignedMessage,
+      ) => {
+        await commCoreModule.initializeCryptoAccount();
+        const [
+          { blobPayload, signature, primaryIdentityPublicKeys },
+          { contentOneTimeKeys, notificationsOneTimeKeys },
+          prekeys,
+        ] = await Promise.all([
+          commCoreModule.getUserPublicKey(),
+          commCoreModule.getOneTimeKeys(ONE_TIME_KEYS_NUMBER),
+          commCoreModule.validateAndGetPrekeys(),
+        ]);
+        const challengeResponse = JSON.stringify(nonceChallengeResponse);
+        const registrationResult =
+          await commRustModule.uploadSecondaryDeviceKeysAndLogIn(
+            userID,
+            challengeResponse,
+            blobPayload,
+            signature,
+            prekeys.contentPrekey,
+            prekeys.contentPrekeySignature,
+            prekeys.notifPrekey,
+            prekeys.notifPrekeySignature,
+            getOneTimeKeyValues(contentOneTimeKeys),
+            getOneTimeKeyValues(notificationsOneTimeKeys),
+          );
+        const { accessToken: token } = JSON.parse(registrationResult);
+
+        const identityAuthResult = { accessToken: token, userID, username: '' };
+        const validatedResult = assertWithValidator(
+          identityAuthResult,
+          identityAuthResultValidator,
+        );
+
+        await commCoreModule.setCommServicesAuthMetadata(
+          validatedResult.userID,
+          primaryIdentityPublicKeys.ed25519,
+          validatedResult.accessToken,
+        );
+
+        return validatedResult;
+      },
       generateNonce: commRustModule.generateNonce,
       getDeviceListHistoryForUser: async (
         userID: string,
diff --git a/services/identity/src/database/device_list.rs b/services/identity/src/database/device_list.rs
--- a/services/identity/src/database/device_list.rs
+++ b/services/identity/src/database/device_list.rs
@@ -647,8 +647,6 @@
       .put_item()
       .table_name(devices_table::NAME)
       .set_item(Some(new_device.into()))
-      .expression_attribute_names("#user_id", ATTR_USER_ID)
-      .expression_attribute_names("#item_id", ATTR_ITEM_ID)
       .send()
       .await
       .map_err(|e| {
diff --git a/web/grpc/identity-service-client-proxy.js b/web/grpc/identity-service-client-proxy.js
--- a/web/grpc/identity-service-client-proxy.js
+++ b/web/grpc/identity-service-client-proxy.js
@@ -6,6 +6,7 @@
 } from 'lib/types/crypto-types.js';
 import type {
   SignedDeviceList,
+  SignedMessage,
   IdentityServiceClient,
   IdentityServiceAuthLayer,
   DeviceOlmOutboundKeys,
@@ -101,6 +102,13 @@
     siweSignature: string,
   ) => Promise<IdentityAuthResult> = this.proxyToWorker('logInWalletUser');
 
+  uploadKeysForRegisteredDeviceAndLogIn: (
+    userID: string,
+    nonceChallengeResponse: SignedMessage,
+  ) => Promise<IdentityAuthResult> = this.proxyToWorker(
+    'uploadKeysForRegisteredDeviceAndLogIn',
+  );
+
   generateNonce: () => Promise<string> = this.proxyToWorker('generateNonce');
 
   publishWebPrekeys: (prekeys: SignedPrekeys) => Promise<void> =
diff --git a/web/grpc/identity-service-client-wrapper.js b/web/grpc/identity-service-client-wrapper.js
--- a/web/grpc/identity-service-client-wrapper.js
+++ b/web/grpc/identity-service-client-wrapper.js
@@ -11,6 +11,7 @@
 import {
   type SignedDeviceList,
   signedDeviceListHistoryValidator,
+  type SignedMessage,
   type IdentityServiceAuthLayer,
   type IdentityServiceClient,
   type DeviceOlmOutboundKeys,
@@ -40,6 +41,7 @@
   OpaqueLoginStartRequest,
   Prekey,
   WalletAuthRequest,
+  SecondaryDeviceKeysUploadRequest,
 } from '../protobufs/identity-unauth-structs.cjs';
 import * as IdentityUnauthClient from '../protobufs/identity-unauth.cjs';
 
@@ -423,6 +425,38 @@
     return assertWithValidator(identityAuthResult, identityAuthResultValidator);
   };
 
+  uploadKeysForRegisteredDeviceAndLogIn: (
+    ownerUserID: string,
+    nonceChallengeResponse: SignedMessage,
+  ) => Promise<IdentityAuthResult> = async (
+    ownerUserID,
+    nonceChallengeResponse,
+  ) => {
+    const identityDeviceKeyUpload = await this.getDeviceKeyUpload();
+    const deviceKeyUpload = authDeviceKeyUpload(identityDeviceKeyUpload);
+    const challengeResponse = JSON.stringify(nonceChallengeResponse);
+
+    const request = new SecondaryDeviceKeysUploadRequest();
+    request.setUserId(ownerUserID);
+    request.setChallengeResponse(challengeResponse);
+    request.setDeviceKeyUpload(deviceKeyUpload);
+
+    let response;
+    try {
+      response =
+        await this.unauthClient.uploadKeysForRegisteredDeviceAndLogIn(request);
+    } catch (e) {
+      console.log('Error calling uploadKeysForRegisteredDeviceAndLogIn:', e);
+      throw new Error(getMessageForException(e) ?? 'unknown');
+    }
+
+    const userID = response.getUserId();
+    const accessToken = response.getAccessToken();
+    const identityAuthResult = { accessToken, userID, username: '' };
+
+    return assertWithValidator(identityAuthResult, identityAuthResultValidator);
+  };
+
   generateNonce: () => Promise<string> = async () => {
     const result = await this.unauthClient.generateNonce(new Empty());
     return result.getNonce();