Skip to content

Tauri

gau has first-class Tauri support. This guide explains how to build desktop apps with OAuth, and also shows how to set up the required deep linking to handle OAuth redirects.

This guide assumes you have already completed the setup for your web framework (e.g., SvelteKit).


  1. Initialize Tauri in your project. This will create a src-tauri directory.

    Terminal window
    npx tauri init

    Make sure you have these installed:

    Terminal window
    npm i @tauri-apps/api
    npm i -D @tauri-apps/cli

    Also set up the build config in tauri.conf.json, and follow other Tauri setup steps specific to your frontend framework.

  2. gau requires the following plugins:

    Terminal window
    npm run tauri add os
    npm run tauri add shell
    npm run tauri add deep-link

    And add the single-instance plugin to your src-tauri/Cargo.toml.

    src-tauri/Cargo.toml
    [dependencies]
    # ...
    [target."cfg(any(target_os = \"macos\", windows, target_os = \"linux\"))".dependencies]
    tauri-plugin-single-instance = { version = "2.0.0", features = [ "deep-link" ] }
  3. To handle OAuth callbacks, you need to configure a custom URL scheme. After the auth flow is done, the browser will open your app again with the URL you set (gau://...).

    In src-tauri/tauri.conf.json, add the deep-link plugin and define a scheme. Set this to something unique to your app.

    src-tauri/tauri.conf.json
    {
    // ...
    "plugins": {
    "deep-link": {
    "desktop": {
    "schemes": [
    "gau"
    ]
    }
    }
    }
    }

    You need to configure gau to use this scheme:

    src/lib/auth.svelte.ts
    import { createSvelteAuth } from '@rttnd/gau/client/svelte'
    export const auth = createSvelteAuth({
    scheme: 'gau',
    })

    Add the core:event:default permission to the src-tauri/capabilities/default.json file.

    src-tauri/capabilities/default.json
    {
    // ...
    "permissions": [
    "core:default",
    "core:event:default",
    "os:default",
    "shell:default",
    "deep-link:default"
    ]
    }
  4. Here is a working src-tauri/src/lib.rs to initialize the deep link plugin and listen for incoming URLs. The listener forwards the URL to the frontend via an event.

    src-tauri/src/lib.rs
    use tauri::{Manager, Emitter};
    use tauri_plugin_deep_link::DeepLinkExt;
    #[cfg_attr(mobile, tauri::mobile_entry_point)]
    pub fn run() {
    let mut builder = tauri::Builder::default();
    #[cfg(desktop)]
    {
    builder = builder.plugin(tauri_plugin_single_instance::init(
    |app, _argv, _cwd| {
    if let Some(window) = app.get_webview_window("main") {
    let _ = window.unminimize();
    let _ = window.set_focus();
    }
    },
    ));
    }
    builder
    .plugin(tauri_plugin_deep_link::init())
    .plugin(tauri_plugin_os::init())
    .plugin(tauri_plugin_shell::init())
    .setup(|app| {
    #[cfg(any(windows, target_os = "linux"))]
    #[cfg(debug_assertions)]
    {
    use tauri_plugin_deep_link::DeepLinkExt;
    let _ = app.deep_link().register_all();
    }
    let handle = app.handle().clone();
    let handle_for_closure = handle.clone();
    handle.deep_link().on_open_url(move |event| {
    if let Some(window) = handle_for_closure.get_webview_window("main") {
    if let Some(url) = event.urls().first() {
    let _ = window.emit("deep-link", url.to_string());
    }
    }
    });
    Ok(())
    })
    .run(tauri::generate_context!())
    .expect("error while running tauri application");
    }
  5. The frontend needs to be configured to build as a static single-page app for Tauri, listen for the deep link event, and handle the sign-in flow. If you are building a full-stack app, you can still host your backend, and the Tauri app will communicate with it as well.

    First, update svelte.config.js to use @sveltejs/adapter-static when building for Tauri.

    svelte.config.js
    import process from 'node:process'
    import Auto from '@sveltejs/adapter-auto'
    import Static from '@sveltejs/adapter-static'
    import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'
    const isTauri = !!process.env.TAURI_ENV_PLATFORM
    /** @type {import('@sveltejs/kit').Config} */
    const config = {
    preprocess: vitePreprocess(),
    kit: {
    adapter: isTauri
    ? Static({ fallback: 'index.html' })
    : Auto(),
    },
    }
    export default config

    Configure Vite to use a different port for Tauri. This way you can run your app on localhost:5173 (which serves as the backend) and the Tauri app on localhost:4173.

    vite.config.ts
    import process from 'node:process'
    import { sveltekit } from '@sveltejs/kit/vite'
    import { defineConfig } from 'vite'
    const isTauri = !!process.env.TAURI_ENV_PLATFORM
    export default defineConfig({
    plugins: [sveltekit()],
    server: {
    port: isTauri ? 4173 : 5173,
    strictPort: true,
    },
    })

    Your Tauri development URL in tauri.conf.json should match this port (e.g., http://localhost:4173).

    Create a root layout that disables SSR, as Tauri apps are client-side rendered.

    src/routes/+layout.ts
    export const ssr = false

    The @rttnd/gau/client/svelte package automatically handles the Tauri-specific logic. Your auth.svelte.ts file remains the same.

    Remember that your Tauri app is a static frontend and needs to communicate with your deployed backend.

    src/lib/auth.svelte.ts
    import { PUBLIC_API_URL } from '$env/static/public'
    import { createSvelteAuth } from '@rttnd/gau/client/svelte'
    export const auth = createSvelteAuth({
    scheme: 'gau',
    baseUrl: PUBLIC_API_URL, // for dev, use http://localhost:5173/api/auth
    })

    Finally, you can use the auth store in your components. The signIn method will automatically open the browser for OAuth and the app will receive the token via the deep link.

    src/routes/+page.svelte
    <script>
    import { auth } from '$lib/auth.svelte'
    </script>
    {#if auth.session?.user}
    <p>Welcome, {auth.session.user.name}!</p>
    <button onclick={() => auth.signOut()}>Sign Out</button>
    {:else}
    <button onclick={() => auth.signIn('github')}>Sign in with GitHub</button>
    {/if}
  6. When using Tauri, the request to your authentication backend might not have a standard Origin header. gau’s CSRF protection checks this header for POST requests. You should configure trustHosts in your createAuth options to allow requests from your Tauri app.

    src/lib/server/auth.ts
    import { createAuth } from '@rttnd/gau/core'
    export const auth = createAuth({
    // ... other options
    trustHosts: ['tauri.localhost']
    })

Your application is now configured for desktop authentication with Tauri! When a user clicks a sign-in button, gau will open the system browser for the OAuth flow. After authorization, the provider redirects to a page that triggers the gau:// deep link, sending the session token back to your Tauri app securely.

This setup allows you to deploy the app on the web and build a desktop app too, with the same codebase.