diff --git a/web/src/common/CasdoorAppConnector.js b/web/src/common/CasdoorAppConnector.js
new file mode 100644
index 00000000..f39c32c3
--- /dev/null
+++ b/web/src/common/CasdoorAppConnector.js
@@ -0,0 +1,121 @@
+// Copyright 2024 The Casdoor Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import React from "react";
+import {Alert, Button, QRCode} from "antd";
+import * as Setting from "../Setting";
+import i18next from "i18next";
+
+export const generateCasdoorAppUrl = (accessToken, forQrCode = true) => {
+ let qrUrl = "";
+ let error = null;
+
+ if (!accessToken) {
+ error = i18next.t("general:Access token is empty");
+ return {qrUrl, error};
+ }
+
+ qrUrl = `casdoor-app://login?serverUrl=${window.location.origin}&accessToken=${accessToken}`;
+
+ if (forQrCode && qrUrl.length >= 2000) {
+ qrUrl = "";
+ error = i18next.t("general:QR code is too large");
+ }
+
+ return {qrUrl, error};
+};
+
+export const CasdoorAppQrCode = ({accessToken, icon}) => {
+ const {qrUrl, error} = generateCasdoorAppUrl(accessToken, true);
+
+ if (error) {
+ return ;
+ }
+
+ return (
+
+ );
+};
+
+export const CasdoorAppUrl = ({accessToken}) => {
+ const {qrUrl, error} = generateCasdoorAppUrl(accessToken, false);
+
+ const handleCopyUrl = async() => {
+ if (!window.isSecureContext) {
+ return;
+ }
+
+ try {
+ await navigator.clipboard.writeText(qrUrl);
+ Setting.showMessage("success", i18next.t("general:Copied to clipboard"));
+ } catch (err) {
+ Setting.showMessage("error", i18next.t("general:Failed to copy"));
+ }
+ };
+
+ if (error) {
+ return ;
+ }
+
+ return (
+