diff --git a/res/css/components/views/settings/devices/_DeviceDetails.pcss b/res/css/components/views/settings/devices/_DeviceDetails.pcss
index d53dcee02b..47766cec45 100644
--- a/res/css/components/views/settings/devices/_DeviceDetails.pcss
+++ b/res/css/components/views/settings/devices/_DeviceDetails.pcss
@@ -73,3 +73,10 @@ limitations under the License.
         color: $primary-content;
     }
 }
+
+.mx_DeviceDetails_signOutButtonContent {
+    display: flex;
+    flex-direction: row;
+    align-items: center;
+    gap: $spacing-4;
+}
diff --git a/src/components/views/settings/devices/CurrentDeviceSection.tsx b/src/components/views/settings/devices/CurrentDeviceSection.tsx
index 8db70aa2b1..e720b47ede 100644
--- a/src/components/views/settings/devices/CurrentDeviceSection.tsx
+++ b/src/components/views/settings/devices/CurrentDeviceSection.tsx
@@ -28,6 +28,7 @@ import { DeviceWithVerification } from './types';
 interface Props {
     device?: DeviceWithVerification;
     isLoading: boolean;
+    isSigningOut: boolean;
     onVerifyCurrentDevice: () => void;
     onSignOutCurrentDevice: () => void;
 }
@@ -35,6 +36,7 @@ interface Props {
 const CurrentDeviceSection: React.FC<Props> = ({
     device,
     isLoading,
+    isSigningOut,
     onVerifyCurrentDevice,
     onSignOutCurrentDevice,
 }) => {
@@ -58,6 +60,7 @@ const CurrentDeviceSection: React.FC<Props> = ({
             { isExpanded &&
                 <DeviceDetails
                     device={device}
+                    isSigningOut={isSigningOut}
                     onSignOutDevice={onSignOutCurrentDevice}
                 />
             }
diff --git a/src/components/views/settings/devices/DeviceDetails.tsx b/src/components/views/settings/devices/DeviceDetails.tsx
index 48c32ad1a7..c773e2cfdb 100644
--- a/src/components/views/settings/devices/DeviceDetails.tsx
+++ b/src/components/views/settings/devices/DeviceDetails.tsx
@@ -19,16 +19,16 @@ import React from 'react';
 import { formatDate } from '../../../../DateUtils';
 import { _t } from '../../../../languageHandler';
 import AccessibleButton from '../../elements/AccessibleButton';
+import Spinner from '../../elements/Spinner';
 import Heading from '../../typography/Heading';
 import { DeviceVerificationStatusCard } from './DeviceVerificationStatusCard';
 import { DeviceWithVerification } from './types';
 
 interface Props {
     device: DeviceWithVerification;
+    isSigningOut: boolean;
     onVerifyDevice?: () => void;
-    // @TODO(kerry) optional while signout only implemented
-    // for current device (PSG-744)
-    onSignOutDevice?: () => void;
+    onSignOutDevice: () => void;
 }
 
 interface MetadataTable {
@@ -38,6 +38,7 @@ interface MetadataTable {
 
 const DeviceDetails: React.FC<Props> = ({
     device,
+    isSigningOut,
     onVerifyDevice,
     onSignOutDevice,
 }) => {
@@ -87,15 +88,19 @@ const DeviceDetails: React.FC<Props> = ({
             </table>,
             ) }
         </section>
-        { !!onSignOutDevice && <section className='mx_DeviceDetails_section'>
+        <section className='mx_DeviceDetails_section'>
             <AccessibleButton
                 onClick={onSignOutDevice}
                 kind='danger_inline'
+                disabled={isSigningOut}
                 data-testid='device-detail-sign-out-cta'
             >
-                { _t('Sign out of this session') }
+                <span className='mx_DeviceDetails_signOutButtonContent'>
+                    { _t('Sign out of this session') }
+                    { isSigningOut && <Spinner w={16} h={16} /> }
+                </span>
             </AccessibleButton>
-        </section> }
+        </section>
     </div>;
 };
 
diff --git a/src/components/views/settings/devices/FilteredDeviceList.tsx b/src/components/views/settings/devices/FilteredDeviceList.tsx
index 4ce3e6e7da..74f3f5eebf 100644
--- a/src/components/views/settings/devices/FilteredDeviceList.tsx
+++ b/src/components/views/settings/devices/FilteredDeviceList.tsx
@@ -36,9 +36,11 @@ import {
 interface Props {
     devices: DevicesDictionary;
     expandedDeviceIds: DeviceWithVerification['device_id'][];
+    signingOutDeviceIds: DeviceWithVerification['device_id'][];
     filter?: DeviceSecurityVariation;
     onFilterChange: (filter: DeviceSecurityVariation | undefined) => void;
     onDeviceExpandToggle: (deviceId: DeviceWithVerification['device_id']) => void;
+    onSignOutDevices: (deviceIds: DeviceWithVerification['device_id'][]) => void;
     onRequestDeviceVerification?: (deviceId: DeviceWithVerification['device_id']) => void;
 }
 
@@ -132,10 +134,16 @@ const NoResults: React.FC<NoResultsProps> = ({ filter, clearFilter }) =>
 const DeviceListItem: React.FC<{
     device: DeviceWithVerification;
     isExpanded: boolean;
+    isSigningOut: boolean;
     onDeviceExpandToggle: () => void;
+    onSignOutDevice: () => void;
     onRequestDeviceVerification?: () => void;
 }> = ({
-    device, isExpanded, onDeviceExpandToggle,
+    device,
+    isExpanded,
+    isSigningOut,
+    onDeviceExpandToggle,
+    onSignOutDevice,
     onRequestDeviceVerification,
 }) => <li className='mx_FilteredDeviceList_listItem'>
     <DeviceTile
@@ -146,7 +154,15 @@ const DeviceListItem: React.FC<{
             onClick={onDeviceExpandToggle}
         />
     </DeviceTile>
-    { isExpanded && <DeviceDetails device={device} onVerifyDevice={onRequestDeviceVerification} /> }
+    {
+        isExpanded &&
+        <DeviceDetails
+            device={device}
+            isSigningOut={isSigningOut}
+            onVerifyDevice={onRequestDeviceVerification}
+            onSignOutDevice={onSignOutDevice}
+        />
+    }
 </li>;
 
 /**
@@ -158,8 +174,10 @@ export const FilteredDeviceList =
         devices,
         filter,
         expandedDeviceIds,
+        signingOutDeviceIds,
         onFilterChange,
         onDeviceExpandToggle,
+        onSignOutDevices,
         onRequestDeviceVerification,
     }: Props, ref: ForwardedRef<HTMLDivElement>) => {
         const sortedDevices = getFilteredSortedDevices(devices, filter);
@@ -213,7 +231,9 @@ export const FilteredDeviceList =
                     key={device.device_id}
                     device={device}
                     isExpanded={expandedDeviceIds.includes(device.device_id)}
+                    isSigningOut={signingOutDeviceIds.includes(device.device_id)}
                     onDeviceExpandToggle={() => onDeviceExpandToggle(device.device_id)}
+                    onSignOutDevice={() => onSignOutDevices([device.device_id])}
                     onRequestDeviceVerification={
                         onRequestDeviceVerification
                             ? () => onRequestDeviceVerification(device.device_id)
diff --git a/src/components/views/settings/devices/useOwnDevices.ts b/src/components/views/settings/devices/useOwnDevices.ts
index 1a4b1e6bb2..b4e3391860 100644
--- a/src/components/views/settings/devices/useOwnDevices.ts
+++ b/src/components/views/settings/devices/useOwnDevices.ts
@@ -18,7 +18,6 @@ import { useCallback, useContext, useEffect, useState } from "react";
 import { IMyDevice, MatrixClient } from "matrix-js-sdk/src/matrix";
 import { CrossSigningInfo } from "matrix-js-sdk/src/crypto/CrossSigning";
 import { VerificationRequest } from "matrix-js-sdk/src/crypto/verification/request/VerificationRequest";
-import { User } from "matrix-js-sdk/src/models/user";
 import { MatrixError } from "matrix-js-sdk/src/http-api";
 import { logger } from "matrix-js-sdk/src/logger";
 
@@ -74,10 +73,9 @@ export enum OwnDevicesError {
     Unsupported = 'Unsupported',
     Default = 'Default',
 }
-type DevicesState = {
+export type DevicesState = {
     devices: DevicesDictionary;
     currentDeviceId: string;
-    currentUserMember?: User;
     isLoading: boolean;
     // not provided when current session cannot request verification
     requestDeviceVerification?: (deviceId: DeviceWithVerification['device_id']) => Promise<VerificationRequest>;
@@ -135,7 +133,6 @@ export const useOwnDevices = (): DevicesState => {
     return {
         devices,
         currentDeviceId,
-        currentUserMember: userId && matrixClient.getUser(userId) || undefined,
         requestDeviceVerification,
         refreshDevices,
         isLoading,
diff --git a/src/components/views/settings/tabs/user/SessionManagerTab.tsx b/src/components/views/settings/tabs/user/SessionManagerTab.tsx
index 14521f84da..0b2056b63d 100644
--- a/src/components/views/settings/tabs/user/SessionManagerTab.tsx
+++ b/src/components/views/settings/tabs/user/SessionManagerTab.tsx
@@ -14,10 +14,12 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
-import React, { useCallback, useEffect, useRef, useState } from 'react';
+import React, { useCallback, useContext, useEffect, useRef, useState } from 'react';
+import { MatrixClient } from 'matrix-js-sdk/src/client';
+import { logger } from 'matrix-js-sdk/src/logger';
 
 import { _t } from "../../../../../languageHandler";
-import { useOwnDevices } from '../../devices/useOwnDevices';
+import { DevicesState, useOwnDevices } from '../../devices/useOwnDevices';
 import SettingsSubsection from '../../shared/SettingsSubsection';
 import { FilteredDeviceList } from '../../devices/FilteredDeviceList';
 import CurrentDeviceSection from '../../devices/CurrentDeviceSection';
@@ -28,12 +30,64 @@ import Modal from '../../../../../Modal';
 import SetupEncryptionDialog from '../../../dialogs/security/SetupEncryptionDialog';
 import VerificationRequestDialog from '../../../dialogs/VerificationRequestDialog';
 import LogoutDialog from '../../../dialogs/LogoutDialog';
+import MatrixClientContext from '../../../../../contexts/MatrixClientContext';
+import { deleteDevicesWithInteractiveAuth } from '../../devices/deleteDevices';
+
+const useSignOut = (
+    matrixClient: MatrixClient,
+    refreshDevices: DevicesState['refreshDevices'],
+): {
+        onSignOutCurrentDevice: () => void;
+        onSignOutOtherDevices: (deviceIds: DeviceWithVerification['device_id'][]) => Promise<void>;
+        signingOutDeviceIds: DeviceWithVerification['device_id'][];
+    } => {
+    const [signingOutDeviceIds, setSigningOutDeviceIds] = useState<DeviceWithVerification['device_id'][]>([]);
+
+    const onSignOutCurrentDevice = () => {
+        Modal.createDialog(
+            LogoutDialog,
+            {}, // props,
+            undefined, // className
+            false, // isPriority
+            true, // isStatic
+        );
+    };
+
+    const onSignOutOtherDevices = async (deviceIds: DeviceWithVerification['device_id'][]) => {
+        if (!deviceIds.length) {
+            return;
+        }
+        try {
+            setSigningOutDeviceIds([...signingOutDeviceIds, ...deviceIds]);
+            await deleteDevicesWithInteractiveAuth(
+                matrixClient,
+                deviceIds,
+                async (success) => {
+                    if (success) {
+                        // @TODO(kerrya) clear selection if was bulk deletion
+                        // when added in PSG-659
+                        await refreshDevices();
+                    }
+                    setSigningOutDeviceIds(signingOutDeviceIds.filter(deviceId => !deviceIds.includes(deviceId)));
+                },
+            );
+        } catch (error) {
+            logger.error("Error deleting sessions", error);
+            setSigningOutDeviceIds(signingOutDeviceIds.filter(deviceId => !deviceIds.includes(deviceId)));
+        }
+    };
+
+    return {
+        onSignOutCurrentDevice,
+        onSignOutOtherDevices,
+        signingOutDeviceIds,
+    };
+};
 
 const SessionManagerTab: React.FC = () => {
     const {
         devices,
         currentDeviceId,
-        currentUserMember,
         isLoading,
         requestDeviceVerification,
         refreshDevices,
@@ -43,6 +97,10 @@ const SessionManagerTab: React.FC = () => {
     const filteredDeviceListRef = useRef<HTMLDivElement>(null);
     const scrollIntoViewTimeoutRef = useRef<ReturnType<typeof setTimeout>>();
 
+    const matrixClient = useContext(MatrixClientContext);
+    const userId = matrixClient.getUserId();
+    const currentUserMember = userId && matrixClient.getUser(userId) || undefined;
+
     const onDeviceExpandToggle = (deviceId: DeviceWithVerification['device_id']): void => {
         if (expandedDeviceIds.includes(deviceId)) {
             setExpandedDeviceIds(expandedDeviceIds.filter(id => id !== deviceId));
@@ -91,14 +149,11 @@ const SessionManagerTab: React.FC = () => {
         });
     }, [requestDeviceVerification, refreshDevices, currentUserMember]);
 
-    const onSignOutCurrentDevice = () => {
-        if (!currentDevice) {
-            return;
-        }
-        Modal.createDialog(LogoutDialog,
-            /* props= */{}, /* className= */undefined,
-            /* isPriority= */false, /* isStatic= */true);
-    };
+    const {
+        onSignOutCurrentDevice,
+        onSignOutOtherDevices,
+        signingOutDeviceIds,
+    } = useSignOut(matrixClient, refreshDevices);
 
     useEffect(() => () => {
         clearTimeout(scrollIntoViewTimeoutRef.current);
@@ -113,6 +168,7 @@ const SessionManagerTab: React.FC = () => {
         <CurrentDeviceSection
             device={currentDevice}
             isLoading={isLoading}
+            isSigningOut={signingOutDeviceIds.includes(currentDevice?.device_id)}
             onVerifyCurrentDevice={onVerifyCurrentDevice}
             onSignOutCurrentDevice={onSignOutCurrentDevice}
         />
@@ -130,9 +186,11 @@ const SessionManagerTab: React.FC = () => {
                     devices={otherDevices}
                     filter={filter}
                     expandedDeviceIds={expandedDeviceIds}
+                    signingOutDeviceIds={signingOutDeviceIds}
                     onFilterChange={setFilter}
                     onDeviceExpandToggle={onDeviceExpandToggle}
                     onRequestDeviceVerification={requestDeviceVerification ? onTriggerDeviceVerification : undefined}
+                    onSignOutDevices={onSignOutOtherDevices}
                     ref={filteredDeviceListRef}
                 />
             </SettingsSubsection>
diff --git a/test/components/views/settings/devices/CurrentDeviceSection-test.tsx b/test/components/views/settings/devices/CurrentDeviceSection-test.tsx
index 1fd6b2ec43..a63d96fa07 100644
--- a/test/components/views/settings/devices/CurrentDeviceSection-test.tsx
+++ b/test/components/views/settings/devices/CurrentDeviceSection-test.tsx
@@ -37,6 +37,7 @@ describe('<CurrentDeviceSection />', () => {
         onVerifyCurrentDevice: jest.fn(),
         onSignOutCurrentDevice: jest.fn(),
         isLoading: false,
+        isSigningOut: false,
     };
     const getComponent = (props = {}): React.ReactElement =>
         (<CurrentDeviceSection {...defaultProps} {...props} />);
diff --git a/test/components/views/settings/devices/DeviceDetails-test.tsx b/test/components/views/settings/devices/DeviceDetails-test.tsx
index 95897f69a1..272f781758 100644
--- a/test/components/views/settings/devices/DeviceDetails-test.tsx
+++ b/test/components/views/settings/devices/DeviceDetails-test.tsx
@@ -26,6 +26,8 @@ describe('<DeviceDetails />', () => {
     };
     const defaultProps = {
         device: baseDevice,
+        isSigningOut: false,
+        onSignOutDevice: jest.fn(),
     };
     const getComponent = (props = {}) => <DeviceDetails {...defaultProps} {...props} />;
     // 14.03.2022 16:15
@@ -60,4 +62,14 @@ describe('<DeviceDetails />', () => {
         const { container } = render(getComponent({ device }));
         expect(container).toMatchSnapshot();
     });
+
+    it('disables sign out button while sign out is pending', () => {
+        const device = {
+            ...baseDevice,
+        };
+        const { getByTestId } = render(getComponent({ device, isSigningOut: true }));
+        expect(
+            getByTestId('device-detail-sign-out-cta').getAttribute('aria-disabled'),
+        ).toEqual("true");
+    });
 });
diff --git a/test/components/views/settings/devices/FilteredDeviceList-test.tsx b/test/components/views/settings/devices/FilteredDeviceList-test.tsx
index ef5ae5b18b..02cff73222 100644
--- a/test/components/views/settings/devices/FilteredDeviceList-test.tsx
+++ b/test/components/views/settings/devices/FilteredDeviceList-test.tsx
@@ -43,7 +43,9 @@ describe('<FilteredDeviceList />', () => {
     const defaultProps = {
         onFilterChange: jest.fn(),
         onDeviceExpandToggle: jest.fn(),
+        onSignOutDevices: jest.fn(),
         expandedDeviceIds: [],
+        signingOutDeviceIds: [],
         devices: {
             [unverifiedNoMetadata.device_id]: unverifiedNoMetadata,
             [verifiedNoMetadata.device_id]: verifiedNoMetadata,
diff --git a/test/components/views/settings/devices/__snapshots__/CurrentDeviceSection-test.tsx.snap b/test/components/views/settings/devices/__snapshots__/CurrentDeviceSection-test.tsx.snap
index 5215e7f208..a3c9d7de2b 100644
--- a/test/components/views/settings/devices/__snapshots__/CurrentDeviceSection-test.tsx.snap
+++ b/test/components/views/settings/devices/__snapshots__/CurrentDeviceSection-test.tsx.snap
@@ -110,7 +110,11 @@ HTMLCollection [
         role="button"
         tabindex="0"
       >
-        Sign out of this session
+        <span
+          class="mx_DeviceDetails_signOutButtonContent"
+        >
+          Sign out of this session
+        </span>
       </div>
     </section>
   </div>,
diff --git a/test/components/views/settings/devices/__snapshots__/DeviceDetails-test.tsx.snap b/test/components/views/settings/devices/__snapshots__/DeviceDetails-test.tsx.snap
index 4a3d7afade..6de93f65af 100644
--- a/test/components/views/settings/devices/__snapshots__/DeviceDetails-test.tsx.snap
+++ b/test/components/views/settings/devices/__snapshots__/DeviceDetails-test.tsx.snap
@@ -101,6 +101,22 @@ exports[`<DeviceDetails /> renders a verified device 1`] = `
         </tbody>
       </table>
     </section>
+    <section
+      class="mx_DeviceDetails_section"
+    >
+      <div
+        class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_danger_inline"
+        data-testid="device-detail-sign-out-cta"
+        role="button"
+        tabindex="0"
+      >
+        <span
+          class="mx_DeviceDetails_signOutButtonContent"
+        >
+          Sign out of this session
+        </span>
+      </div>
+    </section>
   </div>
 </div>
 `;
@@ -210,6 +226,22 @@ exports[`<DeviceDetails /> renders device with metadata 1`] = `
         </tbody>
       </table>
     </section>
+    <section
+      class="mx_DeviceDetails_section"
+    >
+      <div
+        class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_danger_inline"
+        data-testid="device-detail-sign-out-cta"
+        role="button"
+        tabindex="0"
+      >
+        <span
+          class="mx_DeviceDetails_signOutButtonContent"
+        >
+          Sign out of this session
+        </span>
+      </div>
+    </section>
   </div>
 </div>
 `;
@@ -315,6 +347,22 @@ exports[`<DeviceDetails /> renders device without metadata 1`] = `
         </tbody>
       </table>
     </section>
+    <section
+      class="mx_DeviceDetails_section"
+    >
+      <div
+        class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_danger_inline"
+        data-testid="device-detail-sign-out-cta"
+        role="button"
+        tabindex="0"
+      >
+        <span
+          class="mx_DeviceDetails_signOutButtonContent"
+        >
+          Sign out of this session
+        </span>
+      </div>
+    </section>
   </div>
 </div>
 `;
diff --git a/test/components/views/settings/tabs/user/SessionManagerTab-test.tsx b/test/components/views/settings/tabs/user/SessionManagerTab-test.tsx
index ee538b27dd..4e0a556818 100644
--- a/test/components/views/settings/tabs/user/SessionManagerTab-test.tsx
+++ b/test/components/views/settings/tabs/user/SessionManagerTab-test.tsx
@@ -21,6 +21,7 @@ import { DeviceInfo } from 'matrix-js-sdk/src/crypto/deviceinfo';
 import { logger } from 'matrix-js-sdk/src/logger';
 import { DeviceTrustLevel } from 'matrix-js-sdk/src/crypto/CrossSigning';
 import { VerificationRequest } from 'matrix-js-sdk/src/crypto/verification/request/VerificationRequest';
+import { sleep } from 'matrix-js-sdk/src/utils';
 
 import SessionManagerTab from '../../../../../../src/components/views/settings/tabs/user/SessionManagerTab';
 import MatrixClientContext from '../../../../../../src/contexts/MatrixClientContext';
@@ -31,8 +32,7 @@ import {
 } from '../../../../../test-utils';
 import Modal from '../../../../../../src/Modal';
 import LogoutDialog from '../../../../../../src/components/views/dialogs/LogoutDialog';
-
-jest.useFakeTimers();
+import { DeviceWithVerification } from '../../../../../../src/components/views/settings/devices/types';
 
 describe('<SessionManagerTab />', () => {
     const aliceId = '@alice:server.org';
@@ -62,6 +62,8 @@ describe('<SessionManagerTab />', () => {
         getStoredDevice: jest.fn(),
         getDeviceId: jest.fn().mockReturnValue(deviceId),
         requestVerification: jest.fn().mockResolvedValue(mockVerificationRequest),
+        deleteMultipleDevices: jest.fn(),
+        generateClientSecret: jest.fn(),
     });
 
     const defaultProps = {};
@@ -72,6 +74,16 @@ describe('<SessionManagerTab />', () => {
             </MatrixClientContext.Provider>
         );
 
+    const toggleDeviceDetails = (
+        getByTestId: ReturnType<typeof render>['getByTestId'],
+        deviceId: DeviceWithVerification['device_id'],
+    ) => {
+        // open device detail
+        const tile = getByTestId(`device-tile-${deviceId}`);
+        const toggle = tile.querySelector('[aria-label="Toggle device details"]') as Element;
+        fireEvent.click(toggle);
+    };
+
     beforeEach(() => {
         jest.clearAllMocks();
         jest.spyOn(logger, 'error').mockRestore();
@@ -83,6 +95,10 @@ describe('<SessionManagerTab />', () => {
         mockCrossSigningInfo.checkDeviceTrust
             .mockReset()
             .mockReturnValue(new DeviceTrustLevel(false, false, false, false));
+
+        mockClient.getDevices
+            .mockReset()
+            .mockResolvedValue({ devices: [alicesMobileDevice] });
     });
 
     it('renders spinner while devices load', () => {
@@ -257,24 +273,18 @@ describe('<SessionManagerTab />', () => {
                 await flushPromisesWithFakeTimers();
             });
 
-            const tile1 = getByTestId(`device-tile-${alicesOlderMobileDevice.device_id}`);
-            const toggle1 = tile1.querySelector('[aria-label="Toggle device details"]') as Element;
-            fireEvent.click(toggle1);
+            toggleDeviceDetails(getByTestId, alicesOlderMobileDevice.device_id);
 
             // device details are expanded
             expect(getByTestId(`device-detail-${alicesOlderMobileDevice.device_id}`)).toBeTruthy();
 
-            const tile2 = getByTestId(`device-tile-${alicesMobileDevice.device_id}`);
-            const toggle2 = tile2.querySelector('[aria-label="Toggle device details"]') as Element;
-            fireEvent.click(toggle2);
+            toggleDeviceDetails(getByTestId, alicesMobileDevice.device_id);
 
             // both device details are expanded
             expect(getByTestId(`device-detail-${alicesOlderMobileDevice.device_id}`)).toBeTruthy();
             expect(getByTestId(`device-detail-${alicesMobileDevice.device_id}`)).toBeTruthy();
 
-            const tile3 = getByTestId(`device-tile-${alicesMobileDevice.device_id}`);
-            const toggle3 = tile3.querySelector('[aria-label="Toggle device details"]') as Element;
-            fireEvent.click(toggle3);
+            toggleDeviceDetails(getByTestId, alicesMobileDevice.device_id);
 
             // alicesMobileDevice was toggled off
             expect(queryByTestId(`device-detail-${alicesMobileDevice.device_id}`)).toBeFalsy();
@@ -294,9 +304,7 @@ describe('<SessionManagerTab />', () => {
                 await flushPromisesWithFakeTimers();
             });
 
-            const tile1 = getByTestId(`device-tile-${alicesOlderMobileDevice.device_id}`);
-            const toggle1 = tile1.querySelector('[aria-label="Toggle device details"]') as Element;
-            fireEvent.click(toggle1);
+            toggleDeviceDetails(getByTestId, alicesOlderMobileDevice.device_id);
 
             // verify device button is not rendered
             expect(queryByTestId(`verification-status-button-${alicesOlderMobileDevice.device_id}`)).toBeFalsy();
@@ -323,9 +331,7 @@ describe('<SessionManagerTab />', () => {
                 await flushPromisesWithFakeTimers();
             });
 
-            const tile1 = getByTestId(`device-tile-${alicesMobileDevice.device_id}`);
-            const toggle1 = tile1.querySelector('[aria-label="Toggle device details"]') as Element;
-            fireEvent.click(toggle1);
+            toggleDeviceDetails(getByTestId, alicesMobileDevice.device_id);
 
             // click verify button from current session section
             fireEvent.click(getByTestId(`verification-status-button-${alicesMobileDevice.device_id}`));
@@ -355,9 +361,7 @@ describe('<SessionManagerTab />', () => {
                 await flushPromisesWithFakeTimers();
             });
 
-            const tile1 = getByTestId(`device-tile-${alicesMobileDevice.device_id}`);
-            const toggle1 = tile1.querySelector('[aria-label="Toggle device details"]') as Element;
-            fireEvent.click(toggle1);
+            toggleDeviceDetails(getByTestId, alicesMobileDevice.device_id);
 
             // reset mock counter before triggering verification
             mockClient.getDevices.mockClear();
@@ -387,10 +391,7 @@ describe('<SessionManagerTab />', () => {
                 await flushPromisesWithFakeTimers();
             });
 
-            // open device detail
-            const tile1 = getByTestId(`device-tile-${alicesDevice.device_id}`);
-            const toggle1 = tile1.querySelector('[aria-label="Toggle device details"]') as Element;
-            fireEvent.click(toggle1);
+            toggleDeviceDetails(getByTestId, alicesDevice.device_id);
 
             const signOutButton = getByTestId('device-detail-sign-out-cta');
             expect(signOutButton).toMatchSnapshot();
@@ -399,5 +400,165 @@ describe('<SessionManagerTab />', () => {
             // logout dialog opened
             expect(modalSpy).toHaveBeenCalledWith(LogoutDialog, {}, undefined, false, true);
         });
+
+        describe('other devices', () => {
+            const interactiveAuthError = { httpStatus: 401, data: { flows: [{ stages: ["m.login.password"] }] } };
+
+            beforeEach(() => {
+                mockClient.deleteMultipleDevices.mockReset();
+            });
+
+            it('deletes a device when interactive auth is not required', async () => {
+                mockClient.deleteMultipleDevices.mockResolvedValue({});
+                mockClient.getDevices
+                    .mockResolvedValueOnce({ devices: [alicesDevice, alicesMobileDevice, alicesOlderMobileDevice] })
+                    // pretend it was really deleted on refresh
+                    .mockResolvedValueOnce({ devices: [alicesDevice, alicesOlderMobileDevice] });
+
+                const { getByTestId } = render(getComponent());
+
+                await act(async () => {
+                    await flushPromisesWithFakeTimers();
+                });
+
+                toggleDeviceDetails(getByTestId, alicesMobileDevice.device_id);
+
+                const deviceDetails = getByTestId(`device-detail-${alicesMobileDevice.device_id}`);
+                const signOutButton = deviceDetails.querySelector(
+                    '[data-testid="device-detail-sign-out-cta"]',
+                ) as Element;
+                fireEvent.click(signOutButton);
+
+                // sign out button is disabled with spinner
+                expect((deviceDetails.querySelector(
+                    '[data-testid="device-detail-sign-out-cta"]',
+                ) as Element).getAttribute('aria-disabled')).toEqual("true");
+                // delete called
+                expect(mockClient.deleteMultipleDevices).toHaveBeenCalledWith(
+                    [alicesMobileDevice.device_id], undefined,
+                );
+
+                await flushPromisesWithFakeTimers();
+
+                // devices refreshed
+                expect(mockClient.getDevices).toHaveBeenCalled();
+            });
+
+            it('deletes a device when interactive auth is required', async () => {
+                mockClient.deleteMultipleDevices
+                    // require auth
+                    .mockRejectedValueOnce(interactiveAuthError)
+                    // then succeed
+                    .mockResolvedValueOnce({});
+
+                mockClient.getDevices
+                    .mockResolvedValueOnce({ devices: [alicesDevice, alicesMobileDevice, alicesOlderMobileDevice] })
+                    // pretend it was really deleted on refresh
+                    .mockResolvedValueOnce({ devices: [alicesDevice, alicesOlderMobileDevice] });
+
+                const { getByTestId, getByLabelText } = render(getComponent());
+
+                await act(async () => {
+                    await flushPromisesWithFakeTimers();
+                });
+
+                // reset mock count after initial load
+                mockClient.getDevices.mockClear();
+
+                toggleDeviceDetails(getByTestId, alicesMobileDevice.device_id);
+
+                const deviceDetails = getByTestId(`device-detail-${alicesMobileDevice.device_id}`);
+                const signOutButton = deviceDetails.querySelector(
+                    '[data-testid="device-detail-sign-out-cta"]',
+                ) as Element;
+                fireEvent.click(signOutButton);
+
+                await flushPromisesWithFakeTimers();
+                // modal rendering has some weird sleeps
+                await sleep(100);
+
+                expect(mockClient.deleteMultipleDevices).toHaveBeenCalledWith(
+                    [alicesMobileDevice.device_id], undefined,
+                );
+
+                const modal = document.getElementsByClassName('mx_Dialog');
+                expect(modal.length).toBeTruthy();
+
+                // fill password and submit for interactive auth
+                act(() => {
+                    fireEvent.change(getByLabelText('Password'), { target: { value: 'topsecret' } });
+                    fireEvent.submit(getByLabelText('Password'));
+                });
+
+                await flushPromisesWithFakeTimers();
+
+                // called again with auth
+                expect(mockClient.deleteMultipleDevices).toHaveBeenCalledWith([alicesMobileDevice.device_id],
+                    { identifier: {
+                        type: "m.id.user", user: aliceId,
+                    }, password: "", type: "m.login.password", user: aliceId,
+                    });
+                // devices refreshed
+                expect(mockClient.getDevices).toHaveBeenCalled();
+            });
+
+            it('clears loading state when device deletion is cancelled during interactive auth', async () => {
+                mockClient.deleteMultipleDevices
+                    // require auth
+                    .mockRejectedValueOnce(interactiveAuthError)
+                    // then succeed
+                    .mockResolvedValueOnce({});
+
+                mockClient.getDevices
+                    .mockResolvedValue({ devices: [alicesDevice, alicesMobileDevice, alicesOlderMobileDevice] });
+
+                const { getByTestId, getByLabelText } = render(getComponent());
+
+                await act(async () => {
+                    await flushPromisesWithFakeTimers();
+                });
+
+                toggleDeviceDetails(getByTestId, alicesMobileDevice.device_id);
+
+                const deviceDetails = getByTestId(`device-detail-${alicesMobileDevice.device_id}`);
+                const signOutButton = deviceDetails.querySelector(
+                    '[data-testid="device-detail-sign-out-cta"]',
+                ) as Element;
+                fireEvent.click(signOutButton);
+
+                // button is loading
+                expect((deviceDetails.querySelector(
+                    '[data-testid="device-detail-sign-out-cta"]',
+                ) as Element).getAttribute('aria-disabled')).toEqual("true");
+
+                await flushPromisesWithFakeTimers();
+                // modal rendering has some weird sleeps
+                await sleep(100);
+
+                expect(mockClient.deleteMultipleDevices).toHaveBeenCalledWith(
+                    [alicesMobileDevice.device_id], undefined,
+                );
+
+                const modal = document.getElementsByClassName('mx_Dialog');
+                expect(modal.length).toBeTruthy();
+
+                // cancel iau by closing modal
+                act(() => {
+                    fireEvent.click(getByLabelText('Close dialog'));
+                });
+
+                await flushPromisesWithFakeTimers();
+
+                // not called again
+                expect(mockClient.deleteMultipleDevices).toHaveBeenCalledTimes(1);
+                // devices not refreshed (not called since initial fetch)
+                expect(mockClient.getDevices).toHaveBeenCalledTimes(1);
+
+                // loading state cleared
+                expect((deviceDetails.querySelector(
+                    '[data-testid="device-detail-sign-out-cta"]',
+                ) as Element).getAttribute('aria-disabled')).toEqual(null);
+            });
+        });
     });
 });
diff --git a/test/components/views/settings/tabs/user/__snapshots__/SessionManagerTab-test.tsx.snap b/test/components/views/settings/tabs/user/__snapshots__/SessionManagerTab-test.tsx.snap
index 6cec91242d..a0b087ce4f 100644
--- a/test/components/views/settings/tabs/user/__snapshots__/SessionManagerTab-test.tsx.snap
+++ b/test/components/views/settings/tabs/user/__snapshots__/SessionManagerTab-test.tsx.snap
@@ -7,7 +7,11 @@ exports[`<SessionManagerTab /> Sign out Signs out of current device 1`] = `
   role="button"
   tabindex="0"
 >
-  Sign out of this session
+  <span
+    class="mx_DeviceDetails_signOutButtonContent"
+  >
+    Sign out of this session
+  </span>
 </div>
 `;