前言
在 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 为例:
-
登录 GitHub 开发者平台,创建 OAuth 应用。
-
配置:
- 回调 URL: 例如
http://localhost:1420/auth/callback - 获取
client_id和client_secret
- 回调 URL: 例如
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 登录的完整流程。关键点包括:
- 启动本地监听服务并生成唯一标识。
- 跳转浏览器完成用户授权。
- 通过监听获取授权结果,完成登录。
这种实现方式简洁灵活,适用于多种 OAuth 场景。
开源
最近,我正在基于 Tauri 开发一款项目,名为 Coco。目前已开源,项目仍在不断完善中,欢迎大家前往支持并为项目点亮免费的 star 🌟!
作为个人的第一个 Tauri 项目,开发过程中也是边探索边学习。希望能与志同道合的朋友一起交流经验、共同成长!
代码中如有问题或不足之处,期待小伙伴们的宝贵建议和指导!
- 官网: coco.rs/
- 前端仓库: github.com/infinilabs/…
- 服务端仓库: github.com/infinilabs/…
非常感谢您的支持与关注!