diff --git a/apps/web/src/connectors/duo-redirect.html b/apps/web/src/connectors/duo-redirect.html
new file mode 100644
index 0000000000..69ed08b6b2
--- /dev/null
+++ b/apps/web/src/connectors/duo-redirect.html
@@ -0,0 +1,28 @@
+
+
+
+
+
+ Bitwarden Duo Redirect Connector
+
+
+
+
+
+
+
+
+
+
+
diff --git a/apps/web/src/connectors/duo-redirect.scss b/apps/web/src/connectors/duo-redirect.scss
new file mode 100644
index 0000000000..a4c7f9b25b
--- /dev/null
+++ b/apps/web/src/connectors/duo-redirect.scss
@@ -0,0 +1 @@
+@import "../scss/styles.scss";
diff --git a/apps/web/src/connectors/duo-redirect.ts b/apps/web/src/connectors/duo-redirect.ts
new file mode 100644
index 0000000000..33e19ff23a
--- /dev/null
+++ b/apps/web/src/connectors/duo-redirect.ts
@@ -0,0 +1,36 @@
+import { getQsParam } from "./common";
+
+require("./duo-redirect.scss");
+
+const mobileDesktopCallback = "bitwarden://duo-callback";
+
+window.addEventListener("load", () => {
+ const client = getQsParam("client");
+ const code = getQsParam("duo_code");
+
+ if (client === "browser" || client === "web") {
+ const channel = new BroadcastChannel("duoResult");
+
+ channel.postMessage({ code: code });
+ channel.close();
+
+ const handOffMessage = ("; " + document.cookie)
+ .split("; duoHandOffMessage=")
+ .pop()
+ .split(";")
+ .shift();
+
+ document.cookie = "duoHandOffMessage=;SameSite=strict;max-age=0";
+
+ const content = document.getElementById("content");
+ content.innerHTML = "";
+
+ const p = document.createElement("p");
+ p.className = "text-center";
+ p.innerText = handOffMessage;
+
+ content.appendChild(p);
+ } else if (client === "mobile" || client === "desktop") {
+ document.location.replace(mobileDesktopCallback + "?code=" + encodeURIComponent(code));
+ }
+});
diff --git a/apps/web/webpack.config.js b/apps/web/webpack.config.js
index 0254636895..9b3ad15a11 100644
--- a/apps/web/webpack.config.js
+++ b/apps/web/webpack.config.js
@@ -129,6 +129,11 @@ const plugins = [
filename: "captcha-mobile-connector.html",
chunks: ["connectors/captcha"],
}),
+ new HtmlWebpackPlugin({
+ template: "./src/connectors/duo-redirect.html",
+ filename: "duo-redirect-connector.html",
+ chunks: ["connectors/duo-redirect"],
+ }),
new CopyWebpackPlugin({
patterns: [
{ from: "./src/.nojekyll" },
@@ -317,6 +322,7 @@ const webpackConfig = {
"connectors/duo": "./src/connectors/duo.ts",
"connectors/sso": "./src/connectors/sso.ts",
"connectors/captcha": "./src/connectors/captcha.ts",
+ "connectors/duo-redirect": "./src/connectors/duo-redirect.ts",
theme_head: "./src/theme.js",
},
optimization: {