diff --git a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/blog/BlogInput.kt b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/blog/BlogInput.kt
index a240addce8ffb9da7637e5d27a8f8f47eec8bad2..be9f8a3b4eba5890accb8170c64864718b2d16fc 100644
--- a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/blog/BlogInput.kt
+++ b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/blog/BlogInput.kt
@@ -87,6 +87,7 @@ fun BlogInput(
                         item = selectedBlogPost,
                         onItemRepeat = null,
                         onAuthorClicked = null,
+                        onLinkClicked = null,
                     )
                 }
                 IconButton(
diff --git a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/blog/BlogPostView.kt b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/blog/BlogPostView.kt
index 1c5a54705278d6afeab32800d9c221a16a3daf25..755c06d12907822fbca79a08ddc82fdead961f2e 100644
--- a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/blog/BlogPostView.kt
+++ b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/blog/BlogPostView.kt
@@ -86,7 +86,7 @@ fun main() = preview {
             text = "This is a normal blog post.\n\nIt has one author and no comments.",
             time = System.currentTimeMillis() - 999_000
         )
-        BlogPostView(post, {}, {})
+        BlogPostView(post, {}, {}, {})
         val htmlPost = getRandomBlogPostItem(
             text = """
         <h1>Headline</h1>
@@ -119,13 +119,13 @@ fun main() = preview {
             """.trimIndent(),
             time = System.currentTimeMillis() - 750_000
         )
-        BlogPostView(htmlPost, {}, {})
+        BlogPostView(htmlPost, {}, {}, {})
         val commentPost = getRandomBlogCommentItem(
             parent = post,
             comment = "This is a comment on that first blog post.\n\nIt has two lines as well.",
             time = System.currentTimeMillis() - 500_000
         )
-        BlogPostView(commentPost, {}, {})
+        BlogPostView(commentPost, {}, {}, {})
         BlogPostView(
             getRandomBlogCommentItem(
                 parent = commentPost,
@@ -134,8 +134,9 @@ fun main() = preview {
             ),
             null,
             null,
+            null,
         )
-        BlogPostView(getRandomBlogPost(getRandomString(1337), 1337), null, null)
+        BlogPostView(getRandomBlogPost(getRandomString(1337), 1337), null, null, null)
     }
 }
 
@@ -145,6 +146,7 @@ fun BlogPostView(
     item: BlogPost,
     onItemRepeat: ((BlogPost) -> Unit)?,
     onAuthorClicked: ((GroupId) -> Unit)?,
+    onLinkClicked: ((String) -> Unit)?,
     modifier: Modifier = Modifier,
 ) = Card(modifier = modifier) {
     Row(modifier = Modifier.height(IntrinsicSize.Min)) {
@@ -160,9 +162,7 @@ fun BlogPostView(
                     maxLines = if (onItemRepeat == null) 5 else Int.MAX_VALUE,
                     overflow = TextOverflow.Ellipsis,
                 ) { link ->
-                    // TODO: handle link clicks. Display dialog warning the user about opening an external app
-                    //  with the implication that it can be used to identify the user. Also, make this actually
-                    //  clickable which it is not in the SelectionContainer at the moment.
+                    onLinkClicked?.invoke(link)
                 }
             }
             Spacer(Modifier.height(8.dp))
diff --git a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/blog/BlogScreen.kt b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/blog/BlogScreen.kt
index 90b493351cb6f2c9391e79fb78a370f8b25949ca..1596138b3d8e3f8c9ac5da27ca9314b3543f35fa 100644
--- a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/blog/BlogScreen.kt
+++ b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/blog/BlogScreen.kt
@@ -54,12 +54,22 @@ import org.briarproject.briar.desktop.threadedgroup.sharing.ThreadedGroupSharing
 import org.briarproject.briar.desktop.ui.Constants.HEADER_SIZE
 import org.briarproject.briar.desktop.ui.HorizontalDivider
 import org.briarproject.briar.desktop.ui.getInfoDrawerHandler
+import org.briarproject.briar.desktop.utils.DesktopUtils.browseLinkIfSupported
 import org.briarproject.briar.desktop.utils.InternationalizationUtils.i18n
 import org.briarproject.briar.desktop.utils.InternationalizationUtils.i18nF
 import org.briarproject.briar.desktop.viewmodel.viewModel
 
 @Composable
 fun BlogScreen(viewModel: FeedViewModel = viewModel()) {
+    LinkClickedDialog(
+        link = viewModel.clickedLink.value,
+        visible = viewModel.openLinkWarningDialogVisible.value,
+        onDismissed = { viewModel.dismissOpenLinkWarningDialog() },
+        onConfirmed = {
+            viewModel.dismissOpenLinkWarningDialog()
+            browseLinkIfSupported(viewModel.clickedLink.value)
+        },
+    )
     Scaffold(
         topBar = {
             // only show header if some blog is selected
@@ -93,6 +103,7 @@ fun BlogScreen(viewModel: FeedViewModel = viewModel()) {
                         onItemSelected = viewModel::selectPost,
                         onBlogSelected = if (viewModel.selectedBlog.value == null) viewModel::selectBlog else null,
                         onBlogPostsVisible = viewModel::onPostsVisible,
+                        onLinkClicked = viewModel::showOpenLinkWarningDialog,
                         modifier = Modifier.padding(padding)
                     )
                 }
diff --git a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/blog/FeedScreen.kt b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/blog/FeedScreen.kt
index 049d725120a70e3699564fd7830e46671e8eaf5a..3f31754e601f49a9adc436ebf03efeabdb6ba221 100644
--- a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/blog/FeedScreen.kt
+++ b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/blog/FeedScreen.kt
@@ -66,6 +66,7 @@ fun main() = preview(
         },
         onItemSelected = {},
         onBlogSelected = {},
+        onLinkClicked = {},
         onBlogPostsVisible = {},
     )
 }
@@ -77,6 +78,7 @@ fun FeedScreen(
     onItemSelected: (BlogPost) -> Unit,
     onBlogSelected: ((GroupId) -> Unit)?,
     onBlogPostsVisible: (List<MessageId>) -> Unit,
+    onLinkClicked: ((String) -> Unit)?,
     modifier: Modifier = Modifier,
 ) {
     val scrollState = rememberLazyListState()
@@ -98,6 +100,7 @@ fun FeedScreen(
                     item = item,
                     onItemRepeat = onItemSelected,
                     onAuthorClicked = onBlogSelected,
+                    onLinkClicked = onLinkClicked,
                     modifier = Modifier
                         .heightIn(min = HEADER_SIZE)
                         .fillMaxWidth()
diff --git a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/blog/FeedViewModel.kt b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/blog/FeedViewModel.kt
index 14c720ec5e26efbab66b2062c9b3fbb25654b8d2..e7107d74a8dc5a4c58bae1b5243db43bf740d7fc 100644
--- a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/blog/FeedViewModel.kt
+++ b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/blog/FeedViewModel.kt
@@ -79,6 +79,12 @@ class FeedViewModel @Inject constructor(
     private val _selectedBlog = mutableStateOf<GroupId?>(null)
     val selectedBlog = _selectedBlog.asState()
 
+    private val _openLinkWarningDialogVisible = mutableStateOf(false)
+    val openLinkWarningDialogVisible = _openLinkWarningDialogVisible.asState()
+
+    private val _clickedLink = mutableStateOf("")
+    val clickedLink = _clickedLink.asState()
+
     init {
         runOnDbThreadWithTransaction(true, this::loadAllBlogPosts)
     }
@@ -263,4 +269,13 @@ class FeedViewModel @Inject constructor(
         }
         return UnreadPostInfo(firstUnread, num)
     }
+
+    fun showOpenLinkWarningDialog(link: String) {
+        _clickedLink.value = link
+        _openLinkWarningDialogVisible.value = true
+    }
+
+    fun dismissOpenLinkWarningDialog() {
+        _openLinkWarningDialogVisible.value = false
+    }
 }
diff --git a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/blog/LinkClickedDialog.kt b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/blog/LinkClickedDialog.kt
index 26c05c74cd8783dce23d72d23f3789ad5336fc3b..e516440c40ae66fa70f68933e2f313151d44f5d2 100644
--- a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/blog/LinkClickedDialog.kt
+++ b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/blog/LinkClickedDialog.kt
@@ -47,12 +47,9 @@ import androidx.compose.ui.text.font.FontFamily
 import androidx.compose.ui.unit.dp
 import org.briarproject.briar.desktop.theme.codeBackground
 import org.briarproject.briar.desktop.ui.Tooltip
+import org.briarproject.briar.desktop.utils.DesktopUtils.browseLinkIfSupported
 import org.briarproject.briar.desktop.utils.InternationalizationUtils.i18n
 import org.briarproject.briar.desktop.utils.PreviewUtils.preview
-import java.awt.Desktop
-import java.awt.Desktop.getDesktop
-import java.awt.Desktop.isDesktopSupported
-import java.net.URI
 
 @Suppress("HardCodedStringLiteral")
 fun main() = preview("visible" to true) {
@@ -65,9 +62,7 @@ fun main() = preview("visible" to true) {
         },
         onConfirmed = {
             setBooleanParameter("visible", false)
-            if (isDesktopSupported() && getDesktop().isSupported(Desktop.Action.BROWSE)) {
-                getDesktop().browse(URI(link))
-            }
+            browseLinkIfSupported(link)
         }
     )
 }
diff --git a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/utils/DesktopUtils.kt b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/utils/DesktopUtils.kt
new file mode 100644
index 0000000000000000000000000000000000000000..a3da319bd95675eb7cb46fe89e3922e644bc3b50
--- /dev/null
+++ b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/utils/DesktopUtils.kt
@@ -0,0 +1,32 @@
+/*
+ * Briar Desktop
+ * Copyright (C) 2021-2022 The Briar Project
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package org.briarproject.briar.desktop.utils
+
+import java.awt.Desktop
+import java.awt.Desktop.getDesktop
+import java.awt.Desktop.isDesktopSupported
+import java.net.URI
+
+object DesktopUtils {
+    fun browseLinkIfSupported(link: String) {
+        if (isDesktopSupported() && getDesktop().isSupported(Desktop.Action.BROWSE)) {
+            getDesktop().browse(URI(link))
+        }
+    }
+}