Tauri(七)—— APP 端监听 Web 端授权实现 OAuth 登录功能

1,164 阅读3分钟

前言

Tauri 中实现 OAuth 登录,需要结合前端与后端的功能,通过 WebView 与系统浏览器实现安全的用户认证。本文将介绍两种实现方式,并重点解析通过 App 内部监听的方式实现 OAuth 登录的完整流程。

实现思路

1. 通过浏览器唤起桌面应用

  • 用户点击登录按钮,Tauri 打开默认浏览器访问 OAuth 服务端的授权 URL。
  • 用户完成授权后,服务端通过回调 URL 将授权码(code)返回。
  • 应用从回调中获取 code,并通过 API 请求换取 access_token
  • 保存用户信息和 access_token,完成登录。

2. App 内部监听

  • 用户点击登录按钮,Tauri 打开默认浏览器访问 OAuth 服务端的授权 URL。
  • 用户完成授权后,服务端通过回调 URL 将授权码(code)返回。
  • 应用通过监听功能从本地端口中获取 code,并通过 API 请求换取 access_token
  • 保存用户信息和 access_token,完成登录。

区别

第一种方式通过回调获取参数,第二种方式通过 App 内部监听完成。本文将重点介绍第二种方式。

OAuth 登录流程

接下来以 GitHub OAuth 为例,讲解如何实现完整的 OAuth 登录。

1. Tauri APP 前端实现

登录按钮与授权逻辑

import { shell } from "@tauri-apps/api";
import { listen } from "@tauri-apps/api/event";
import { invoke } from "@tauri-apps/api/tauri";
import { getCurrentWindow } from "@tauri-apps/api/window";

async function signIn() {
  let resolvePromise: (url: URL) => void;

  try {
    // 开启事件监听
    const stopListening = await listen("oauth://url", (data: { payload: string }) => {
      const urlObject = new URL(data.payload);
      resolvePromise(urlObject);
    });

    // 停止可能存在的 OAuth 服务
    try {
      await invoke("plugin:oauth|stop");
    } catch (e) {
      // 忽略错误
    }

    // 启动 OAuth 服务
    const port: string = await invoke("plugin:oauth|start", {
      config: {
        response: callbackTemplate,
        headers: {
          "Content-Type": "text/html; charset=utf-8",
          "Cache-Control": "no-store, no-cache, must-revalidate",
          Pragma: "no-cache",
        },
        cleanup: true,
      },
    });

    const uid = uuidv4(); // 生成业务需求的唯一 ID
    await shell.open(`http://localhost:1420/login?provider=coco-cloud&request_id=${uid}&port=${port}`);

    // 获取监听回调的 URL
    const url = await new Promise<URL>((r) => {
      resolvePromise = r;
    });
    stopListening();

    // 获取回调的 code 参数
    const code = url.searchParams.get("code");
    if (!code) throw new Error("Invalid token or expired");

    // 请求后端接口进行认证
    const response: any = await tauriFetch({
      url: `/auth/request_access_token?request_id=${uid}`,
      method: "GET",
      headers: { "X-API-TOKEN": code },
    });

    await setAuth({ token: response.access_token, expires: response.expire_at });
    await getCurrentWindow().setFocus();
  } catch (error) {
    console.error("Sign in failed:", error);
    await setAuth(undefined);
    throw error;
  }
}

// 监听登录状态变化
useEffect(() => {
  const setupAuthListener = async () => {
    if (!auth) {
      // navigate("/signin", { replace: true });
    }
  };

  setupAuthListener();

  return () => {
    invoke("plugin:oauth|stop").catch(() => {});
  };
}, [auth]);

2. 配置 OAuth 提供商

GitHub OAuth 为例:

  1. 登录 GitHub 开发者平台,创建 OAuth 应用。

  2. 配置:

    • 回调 URL: 例如 http://localhost:1420/auth/callback
    • 获取 client_idclient_secret

3. Web 浏览器端实现

授权页面逻辑

import { useSearchParams } from "react-router-dom";

const [searchParams] = useSearchParams();
const uid = searchParams.get("request_id");
const port = searchParams.get("port");

const authWithGithub = (uid: string, port: string) => {
  const authorizeUrl = "https://github.com/login/oauth/authorize";
  window.location.href = `${authorizeUrl}?client_id=YOUR_CLIENT_ID&redirect_uri=http://localhost:1420/login?port=${port}`;
};

function handleGithubSignIn() {
  uid && authWithGithub(uid, port!);
}

// 回调处理
useEffect(() => {
  const code = searchParams.get("code");
  if (code) {
    setTimeout(() => {
      window.location.href = `http://localhost:${port}/?code=${code}&provider=coco-cloud`;
    }, 1000);
  }
}, []);

小结

通过 Tauri 的监听功能与 Web 浏览器的联动,可以实现 OAuth 登录的完整流程。关键点包括:

  1. 启动本地监听服务并生成唯一标识。
  2. 跳转浏览器完成用户授权。
  3. 通过监听获取授权结果,完成登录。

这种实现方式简洁灵活,适用于多种 OAuth 场景。

开源

最近,我正在基于 Tauri 开发一款项目,名为 Coco。目前已开源,项目仍在不断完善中,欢迎大家前往支持并为项目点亮免费的 star 🌟!

作为个人的第一个 Tauri 项目,开发过程中也是边探索边学习。希望能与志同道合的朋友一起交流经验、共同成长!

代码中如有问题或不足之处,期待小伙伴们的宝贵建议和指导!

非常感谢您的支持与关注!