Tauri
gau has first-class Tauri support. This guide explains how to build desktop and mobile apps with OAuth, and shows how to set up deep linking to handle OAuth redirects.
This guide assumes you have already completed the setup for your web framework (e.g., SvelteKit).
-
Initialize Tauri
Section titled “Initialize Tauri”Initialize Tauri in your project. This will create a
src-tauridirectory.Terminal window npx tauri initTerminal window pnpm tauri initTerminal window yarn tauri initTerminal window bun tauri initTerminal window nlx tauri initMake sure you have these installed:
Terminal window npm i @tauri-apps/apinpm i -D @tauri-apps/cliTerminal window pnpm add @tauri-apps/apipnpm add -D @tauri-apps/cliTerminal window yarn add @tauri-apps/apiyarn add -D @tauri-apps/cliTerminal window bun add @tauri-apps/apibun add -D @tauri-apps/cliTerminal window ni @tauri-apps/apini -D @tauri-apps/cliAlso set up the
buildconfig intauri.conf.json, and follow other Tauri setup steps specific to your frontend framework. -
Install Tauri Plugins
Section titled “Install Tauri Plugins”gaurequires the following plugins:- Single Instance (recommended on desktop)
- Opener
- Deep Link
Terminal window npm run tauri add openernpm run tauri add deep-linkTerminal window pnpm tauri add openerpnpm tauri add deep-linkTerminal window yarn run tauri add openeryarn run tauri add deep-linkTerminal window bun tauri add openerbun tauri add deep-linkTerminal window nlx tauri add openernlx tauri add deep-linkAnd add the
single-instanceplugin to yoursrc-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" ] } -
Configure Deep Linking
Section titled “Configure Deep Linking”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 thedeep-linkplugin 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
gauto use this scheme:src/routes/+layout.svelte <script lang="ts">import AuthProvider from '@rttnd/gau/client/svelte/AuthProvider.svelte'const { children } = $props()</script><AuthProvider><AuthProvider scheme="gau">{@render children()}</AuthProvider>src/app.tsx import { AuthProvider } from '@rttnd/gau/client/solid'export default function App() {return (<AuthProvider><AuthProvider scheme="gau">...</AuthProvider>)}src/auth/client.ts import process from 'node:process'import { createAuthClient } from '@rttnd/gau/client/vanilla'export const authClient = createAuthClient({baseUrl: process.env.AUTH_BASE_URL, // http://localhost:3000/api/auth in devscheme: 'gau',})Add required permissions to your Tauri capabilities:
src-tauri/capabilities/default.json {// ..."permissions": ["core:default","core:event:default","deep-link:default","opener:default"]}src-tauri/capabilities/mobile.json {"$schema": "../gen/schemas/mobile-schema.json","identifier": "mobile-capability","windows": ["main"],"platforms": ["iOS","android"],"permissions": ["core:event:default","deep-link:default","opener:default",{"identifier": "opener:allow-open-url","allow": [{"url": "*"}]}]} -
Set up Rust App Backend
Section titled “Set up Rust App Backend”Here is a working
src-tauri/src/lib.rsto 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_opener::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");} -
Configure the Frontend
Section titled “Configure the Frontend”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.jsto use@sveltejs/adapter-staticwhen 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 configConfigure Vite for Tauri. On mobile, Tauri v2 sets
TAURI_DEV_HOSTto the host it exposes to the device and tunnels traffic. Use it forserver.hostand HMR; on desktop you can use the usual local ports.vite.config.ts import process from 'node:process'import { sveltekit } from '@sveltejs/kit/vite'import { defineConfig } from 'vite'const isTauri = !!process.env.TAURI_ENV_PLATFORMexport default defineConfig({plugins: [sveltekit()],server: {port: isTauri ? 4173 : 5173,strictPort: true,},})vite.config.ts import process from 'node:process'import { sveltekit } from '@sveltejs/kit/vite'import { defineConfig } from 'vite'const host = process.env.TAURI_DEV_HOSTconst isTauri = !!process.env.TAURI_ENV_PLATFORMconst devPort = 1420const hmrPort = 1430export default defineConfig({plugins: [sveltekit()],clearScreen: false,server: {port: isTauri ? devPort : 5173,strictPort: true,host: host || false,hmr: host? {protocol: 'ws',host,port: hmrPort,}: undefined,},})Your Tauri development URL in
tauri.conf.jsonshould 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 = falseThe
@rttnd/gau/client/sveltepackage automatically handles the Tauri-specific logic. Remember that your Tauri app is a static frontend and needs to communicate with your deployed backend.Use envs to set the
baseUrlfor development (http://localhost:5173/api/auth) and production.src/routes/+layout.svelte <script lang='ts'>import { PUBLIC_API_URL } from '$env/static/public'import AuthProvider from '@rttnd/gau/client/svelte/AuthProvider.svelte'const { children } = $props()</script><AuthProvider scheme="gau" baseUrl={PUBLIC_API_URL}>{@render children()}</AuthProvider>Finally, you can use the
useAuthhook in your components. ThesignInmethod will automatically open the browser for the OAuth flow, and the app will receive the auth code via the deep link and exchange it for a session token.src/routes/+page.svelte <script>import { useAuth } from '@rttnd/gau/client/svelte'const { auth } = useAuth()</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}The vanilla client ships with helpers tailored for Tauri. Listen for tokens delivered via the Tauri deep link bridge with
startTauriBridge.src/main.ts import { createAuthClient } from '@rttnd/gau/client/vanilla'const authClient = createAuthClient({baseUrl: import.meta.env.VITE_API_URL,scheme: 'gau',})await authClient.startTauriBridge()document.getElementById('sign-in-github')?.addEventListener('click', async () => {await authClient.signIn('github')})document.getElementById('sign-out')?.addEventListener('click', async () => {await authClient.signOut()})TODO
-
Configure the Backend
Section titled “Configure the Backend”When using Tauri, the request to your authentication backend might not have a standard
Originheader.gau’s CSRF protection checks this header forPOSTrequests. You should configuretrustHostsin yourcreateAuthoptions to allow requests from your Tauri app.src/lib/server/auth.ts import { createAuth } from '@rttnd/gau'export const auth = createAuth({// ... other optionstrustHosts: ['tauri.localhost']}) -
Android Development Notes
Section titled “Android Development Notes”See the README.
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 an authorization code back to your Tauri app. The app then exchanges this code for a session token using PKCE, ensuring a secure handoff.
This setup allows you to deploy the app on the web and build a desktop app too, with the same codebase.