Introduction

Implementing OAuth login in Tauri requires integrating both frontend and backend functionalities. It uses WebView and the system browser to securely authenticate users. This article outlines a complete implementation process with key steps.

Implementation Overview

1. Launching Desktop Applications via Browser

  • The user clicks the login button, and Tauri opens the default browser to access the OAuth server's authorization URL.
  • After completing the authorization, the server returns an authorization code (code) via a callback URL.
  • The application retrieves the code from the callback and exchanges it for an access_token via an API request.
  • User information and access_token are stored, completing the login process.

2. Internal App Listening

  • The user clicks the login button, and Tauri opens the default browser to access the OAuth server's authorization URL.
  • After completing the authorization, the server returns an authorization code (code).
  • The application retrieves the code through an internal listener and exchanges it for an access_token via an API request.
  • User information and access_token are stored, completing the login process.

The difference between these two approaches lies in how the code is obtained: the first method retrieves it from the callback, while the second listens for it internally within the app.

This article focuses on the second approach.

OAuth Login Workflow

The following example demonstrates the implementation process using GitHub OAuth.

Tauri App Frontend Implementation

  1. Login Button Event
async function signIn() {
    let res: (url: URL) => void;

    try {
        const stopListening = await listen(
            "oauth://url",
            (data: { payload: string }) => {
                console.log(111, data.payload);

                const urlObject = new URL(data.payload);
                res(urlObject);
            }
        );

        try {
            await invoke("plugin:oauth|stop");
        } catch (e) {
            // Ignore errors if no server is running
        }

        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,
            },
        });

        let uid = app_uid;
        if (!uid) {
            uid = uuidv4();
            setAppUid(uid);
        }

        await shell.open(
            `http://localhost:1420/login?provider=coco-cloud&product=coco&request_id=${uid}&port=${port}`
        );

        const url = await new Promise<URL>((r) => {
            res = r;
        });
        stopListening();

        const code = url.searchParams.get("code");
        const provider = url.searchParams.get("provider");

        if (!code || provider !== "coco-cloud") {
            throw new Error("Invalid token or expires");
        }

        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,
            plan: { upgraded: false, last_checked: 0 },
        });

        getCurrentWindow()
            .setFocus()
            .catch(() => {});
    } catch (error) {
        console.error("Sign in failed:", error);
        await setAuth(undefined);
        throw error;
    }
}

// Listen for auth status
useEffect(() => {
    const setupAuthListener = async () => {
        try {
            if (!auth) {
                // Redirect to the signin page
            }
        } catch (error) {
            console.error("Failed to set up auth listener:", error);
        }
    };

    setupAuthListener();

    return () => {
        const cleanup = async () => {
            try {
                await invoke("plugin:oauth|stop");
            } catch (e) {}
        };

        cleanup();
    };
}, [auth]);

Configuring OAuth Provider

For GitHub OAuth:

  1. Log in to the GitHub Developer platform and create an OAuth application.
  2. Configure:
    • Callback URL: For example, http://localhost:1420/auth/callback
    • Retrieve client_id and client_secret.

Web Browser Implementation


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

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

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

// GitHub Login
function handleGithubSignIn() {
    uid && authWithGithub(uid, port);
}

// Monitor code and callback to app
useEffect(() => {
    setTimeout(() => {
        window.location.href = `http://localhost:${port}/?code=${code}&provider=coco-cloud`;
    }, 10000);
}, [code]);

Conclusion

This guide demonstrates how to implement OAuth login in a Tauri app using GitHub OAuth. It covers both frontend and backend integration, providing a secure and user-friendly login experience. With this method, developers can adapt the approach to other OAuth providers as needed.

Open Source

Recently, I’ve been working on a project based on Tauri called Coco. It’s open source and under continuous improvement. I’d love your support—please give the project a free star 🌟!

This is my first Tauri project, and I’ve been learning while exploring. I look forward to connecting with like-minded individuals to share experiences and grow together!

Thank you for your support and attention!

Source: View source