diff --git a/lib/components/qr-auth-handler.react.js b/lib/components/qr-auth-handler.react.js
new file mode 100644
--- /dev/null
+++ b/lib/components/qr-auth-handler.react.js
@@ -0,0 +1,147 @@
+// @flow
+
+import invariant from 'invariant';
+import * as React from 'react';
+
+import { IdentityClientContext } from '../shared/identity-client-context.js';
+import { useTunnelbroker } from '../tunnelbroker/tunnelbroker-context.js';
+import {
+  tunnelbrokerMessageTypes,
+  type TunnelbrokerMessage,
+} from '../types/tunnelbroker/messages.js';
+import {
+  peerToPeerMessageTypes,
+  peerToPeerMessageValidator,
+} from '../types/tunnelbroker/peer-to-peer-message-types.js';
+import { qrCodeAuthMessageTypes } from '../types/tunnelbroker/qr-code-auth-message-types.js';
+import {
+  createQRAuthTunnelbrokerMessage,
+  parseQRAuthTunnelbrokerMessage,
+} from '../utils/qr-code-auth.js';
+
+type QRAuthHandlerProps = {
+  +secondaryDeviceID: ?string,
+  +aesKey: ?string,
+  +performSecondaryDeviceRegistration: (userID: string) => Promise<void>,
+};
+
+function QRAuthHandler(props: QRAuthHandlerProps): React.Node {
+  const { secondaryDeviceID, aesKey, performSecondaryDeviceRegistration } =
+    props;
+  const [primaryDeviceID, setPrimaryDeviceID] = React.useState<?string>();
+  const {
+    setUnauthorizedDeviceID,
+    addListener,
+    removeListener,
+    connected: tunnelbrokerConnected,
+    isAuthorized,
+    sendMessage,
+  } = useTunnelbroker();
+
+  const identityContext = React.useContext(IdentityClientContext);
+  const identityClient = identityContext?.identityClient;
+
+  React.useEffect(() => {
+    if (
+      !secondaryDeviceID ||
+      !aesKey ||
+      !tunnelbrokerConnected ||
+      !isAuthorized ||
+      !primaryDeviceID
+    ) {
+      return;
+    }
+
+    void (async () => {
+      const message = createQRAuthTunnelbrokerMessage(aesKey, {
+        type: qrCodeAuthMessageTypes.SECONDARY_DEVICE_REGISTRATION_SUCCESS,
+      });
+      await sendMessage({
+        deviceID: primaryDeviceID,
+        payload: JSON.stringify(message),
+      });
+    })();
+  }, [
+    tunnelbrokerConnected,
+    isAuthorized,
+    sendMessage,
+    primaryDeviceID,
+    aesKey,
+    secondaryDeviceID,
+  ]);
+
+  const tunnelbrokerMessageListener = React.useCallback(
+    async (message: TunnelbrokerMessage) => {
+      invariant(identityClient, 'identity context not set');
+      if (
+        !aesKey ||
+        message.type !== tunnelbrokerMessageTypes.MESSAGE_TO_DEVICE
+      ) {
+        return;
+      }
+
+      let innerMessage;
+      try {
+        innerMessage = JSON.parse(message.payload);
+      } catch {
+        return;
+      }
+      if (
+        !peerToPeerMessageValidator.is(innerMessage) ||
+        innerMessage.type !== peerToPeerMessageTypes.QR_CODE_AUTH_MESSAGE
+      ) {
+        return;
+      }
+      const qrCodeAuthMessage = parseQRAuthTunnelbrokerMessage(
+        aesKey,
+        innerMessage,
+      );
+
+      if (
+        qrCodeAuthMessage?.type ===
+        qrCodeAuthMessageTypes.BACKUP_DATA_KEY_MESSAGE
+      ) {
+        return;
+      }
+
+      if (
+        !qrCodeAuthMessage ||
+        qrCodeAuthMessage.type !==
+          qrCodeAuthMessageTypes.DEVICE_LIST_UPDATE_SUCCESS
+      ) {
+        return;
+      }
+      const { primaryDeviceID: receivedPrimaryDeviceID, userID } =
+        qrCodeAuthMessage;
+      setPrimaryDeviceID(receivedPrimaryDeviceID);
+
+      await performSecondaryDeviceRegistration(userID);
+      setUnauthorizedDeviceID(null);
+    },
+    [
+      setUnauthorizedDeviceID,
+      identityClient,
+      aesKey,
+      performSecondaryDeviceRegistration,
+    ],
+  );
+
+  React.useEffect(() => {
+    if (!secondaryDeviceID) {
+      return undefined;
+    }
+
+    addListener(tunnelbrokerMessageListener);
+    return () => {
+      removeListener(tunnelbrokerMessageListener);
+    };
+  }, [
+    secondaryDeviceID,
+    addListener,
+    removeListener,
+    tunnelbrokerMessageListener,
+  ]);
+  return null;
+}
+
+export { QRAuthHandler };
diff --git a/native/qr-code/qr-code-screen.react.js b/native/qr-code/qr-code-screen.react.js
--- a/native/qr-code/qr-code-screen.react.js
+++ b/native/qr-code/qr-code-screen.react.js
@@ -5,6 +5,7 @@
 import { View, Text } from 'react-native';
 import QRCode from 'react-native-qrcode-svg';
 
+import { QRAuthHandler } from 'lib/components/qr-auth-handler.react.js';
 import { qrCodeLinkURL } from 'lib/facts/links.js';
 import { uintArrayToHexString } from 'lib/media/data-utils.js';
 import { IdentityClientContext } from 'lib/shared/identity-client-context.js';
@@ -13,19 +14,6 @@
   NonceChallenge,
   SignedMessage,
 } from 'lib/types/identity-service-types.js';
-import {
-  tunnelbrokerMessageTypes,
-  type TunnelbrokerMessage,
-} from 'lib/types/tunnelbroker/messages.js';
-import {
-  peerToPeerMessageTypes,
-  peerToPeerMessageValidator,
-} from 'lib/types/tunnelbroker/peer-to-peer-message-types.js';
-import { qrCodeAuthMessageTypes } from 'lib/types/tunnelbroker/qr-code-auth-message-types.js';
-import {
-  createQRAuthTunnelbrokerMessage,
-  parseQRAuthTunnelbrokerMessage,
-} from 'lib/utils/qr-code-auth.js';
 
 import type { QRCodeSignInNavigationProp } from './qr-code-sign-in-navigator.react.js';
 import { commCoreModule } from '../native-modules.js';
@@ -45,89 +33,14 @@
   const [qrCodeValue, setQrCodeValue] = React.useState<?string>();
   const [qrData, setQRData] =
     React.useState<?{ +deviceID: string, +aesKey: string }>();
-  const [primaryDeviceID, setPrimaryDeviceID] = React.useState<?string>();
-  const {
-    setUnauthorizedDeviceID,
-    addListener,
-    removeListener,
-    connected: tunnelbrokerConnected,
-    isAuthorized,
-    sendMessage,
-  } = useTunnelbroker();
+  const { setUnauthorizedDeviceID } = useTunnelbroker();
+
   const identityContext = React.useContext(IdentityClientContext);
   const identityClient = identityContext?.identityClient;
 
-  React.useEffect(() => {
-    if (
-      !tunnelbrokerConnected ||
-      !isAuthorized ||
-      !primaryDeviceID ||
-      !qrData
-    ) {
-      return;
-    }
-
-    const message = createQRAuthTunnelbrokerMessage(qrData.aesKey, {
-      type: qrCodeAuthMessageTypes.SECONDARY_DEVICE_REGISTRATION_SUCCESS,
-    });
-    void sendMessage({
-      deviceID: primaryDeviceID,
-      payload: JSON.stringify(message),
-    });
-  }, [
-    tunnelbrokerConnected,
-    isAuthorized,
-    sendMessage,
-    primaryDeviceID,
-    qrData,
-  ]);
-
-  const tunnelbrokerMessageListener = React.useCallback(
-    async (message: TunnelbrokerMessage) => {
+  const performRegistration = React.useCallback(
+    async (userID: string) => {
       invariant(identityClient, 'identity context not set');
-      if (
-        !qrData ||
-        message.type !== tunnelbrokerMessageTypes.MESSAGE_TO_DEVICE
-      ) {
-        return;
-      }
-
-      let innerMessage;
-      try {
-        innerMessage = JSON.parse(message.payload);
-      } catch {
-        return;
-      }
-      if (
-        !peerToPeerMessageValidator.is(innerMessage) ||
-        innerMessage.type !== peerToPeerMessageTypes.QR_CODE_AUTH_MESSAGE
-      ) {
-        return;
-      }
-      const qrCodeAuthMessage = parseQRAuthTunnelbrokerMessage(
-        qrData.aesKey,
-        innerMessage,
-      );
-
-      if (
-        qrCodeAuthMessage?.type ===
-        qrCodeAuthMessageTypes.BACKUP_DATA_KEY_MESSAGE
-      ) {
-        console.log('Received backup data key:', qrCodeAuthMessage);
-        return;
-      }
-
-      if (
-        !qrCodeAuthMessage ||
-        qrCodeAuthMessage.type !==
-          qrCodeAuthMessageTypes.DEVICE_LIST_UPDATE_SUCCESS
-      ) {
-        return;
-      }
-      const { primaryDeviceID: receivedPrimaryDeviceID, userID } =
-        qrCodeAuthMessage;
-      setPrimaryDeviceID(receivedPrimaryDeviceID);
-
       try {
         const nonce = await identityClient.generateNonce();
         const nonceChallenge: NonceChallenge = { nonce };
@@ -150,28 +63,9 @@
         ]);
       }
     },
-    [setUnauthorizedDeviceID, identityClient, qrData],
+    [setUnauthorizedDeviceID, identityClient],
   );
 
-  React.useEffect(() => {
-    if (!qrData) {
-      return undefined;
-    }
-    addListener(tunnelbrokerMessageListener);
-    setUnauthorizedDeviceID(qrData.deviceID);
-
-    return () => {
-      removeListener(tunnelbrokerMessageListener);
-      setUnauthorizedDeviceID(null);
-    };
-  }, [
-    setUnauthorizedDeviceID,
-    qrData,
-    addListener,
-    removeListener,
-    tunnelbrokerMessageListener,
-  ]);
-
   const generateQRCode = React.useCallback(async () => {
     try {
       const rawAESKey: Uint8Array = await AES.generateKey();
@@ -180,12 +74,13 @@
       const ed25519Key: string = await getContentSigningKey();
 
       const url = qrCodeLinkURL(aesKeyAsHexString, ed25519Key);
+      setUnauthorizedDeviceID(ed25519Key);
       setQrCodeValue(url);
       setQRData({ deviceID: ed25519Key, aesKey: aesKeyAsHexString });
     } catch (err) {
       console.error('Failed to generate QR Code:', err);
     }
-  }, []);
+  }, [setUnauthorizedDeviceID]);
 
   React.useEffect(() => {
     void generateQRCode();
@@ -193,29 +88,36 @@
 
   const styles = useStyles(unboundStyles);
   return (
-    <View style={styles.container}>
-      <Text style={styles.heading}>Log in to Comm</Text>
-      <Text style={styles.headingSubtext}>
-        Open the Comm app on your phone and scan the QR code below
-      </Text>
-      <QRCode value={qrCodeValue} size={200} />
-      <View style={styles.instructionsBox}>
-        <Text style={styles.instructionsTitle}>How to find the scanner:</Text>
-        <Text style={styles.instructionsStep}>
-          <Text>Go to </Text>
-          <Text style={styles.instructionsBold}>Profile</Text>
-        </Text>
-        <Text style={styles.instructionsStep}>
-          <Text>Select </Text>
-          <Text style={styles.instructionsBold}>Linked devices </Text>
-        </Text>
-        <Text style={styles.instructionsStep}>
-          <Text>Click </Text>
-          <Text style={styles.instructionsBold}>Add </Text>
-          <Text>on the top right</Text>
+    <>
+      <QRAuthHandler
+        secondaryDeviceID={qrData?.deviceID}
+        aesKey={qrData?.aesKey}
+        performSecondaryDeviceRegistration={performRegistration}
+      />
+      <View style={styles.container}>
+        <Text style={styles.heading}>Log in to Comm</Text>
+        <Text style={styles.headingSubtext}>
+          Open the Comm app on your phone and scan the QR code below
         </Text>
+        <QRCode value={qrCodeValue} size={200} />
+        <View style={styles.instructionsBox}>
+          <Text style={styles.instructionsTitle}>How to find the scanner:</Text>
+          <Text style={styles.instructionsStep}>
+            <Text>Go to </Text>
+            <Text style={styles.instructionsBold}>Profile</Text>
+          </Text>
+          <Text style={styles.instructionsStep}>
+            <Text>Select </Text>
+            <Text style={styles.instructionsBold}>Linked devices </Text>
+          </Text>
+          <Text style={styles.instructionsStep}>
+            <Text>Click </Text>
+            <Text style={styles.instructionsBold}>Add </Text>
+            <Text>on the top right</Text>
+          </Text>
+        </View>
       </View>
-    </View>
+    </>
   );
 }