DEV Community

Yann Amsellem
Yann Amsellem

Posted on

Adding Auth0 to Your Tauri App: Secure Authentication for Agx on Web and Desktop

Understanding Agx and Its Constraints

At Agnostic, we’ve crafted agx as a modern desktop application powered by Tauri, SvelteKit, and Plot, with ClickHouse driving fast local data processing. Our aim is to give users an intuitive interface to explore and query data effortlessly. What makes agx unique is its ability to operate in two modes:

  • Desktop Mode: A native app using ClickHouse for fast, local data queries.
  • Web Mode: A browser-based interface connecting to a remote server instance.

Delivering a seamless experience across both modes is a challenge, particularly for authentication, which must work smoothly in both native and web environments while prioritizing security and ease of use. For the desktop side, we tap into Tauri’s deep-linking and opener plugins to manage Auth0’s Universal Login flow, using a custom agx://localhost/auth protocol to redirect back to the app.

Why Auth0? The Need for Authentication

To unlock advanced features like agx’s AI Assistant, users need to log in. We chose Auth0 with its Universal Login flow for its:

  • Seamless SSO: Universal Login provides a unified, hosted login experience across web and desktop.
  • Cross-Platform Compatibility: Works with SvelteKit’s frontend and Tauri’s Rust-based backend.
  • Security: Leverages OAuth 2.0, JWT, and secure token storage.
  • Ease of Use: The hosted Universal Login page is easy to set up and customize, letting us align it with agx’s branding.

By combining Auth0 with Tauri’s plugins, we ensure users enjoy a consistent login experience, whether running agx natively or in a browser, unlocking the AI Assistant with minimal hassle.

Configuring Auth0 with Universal Login Step-by-Step

To integrate Auth0’s SPA application type with Universal Login, configure the Auth0 dashboard and your Tauri + SvelteKit project as follows:

Create an Auth0 Application

Start by heading to the Auth0 dashboard and creating a new application. Select Single Page Application (SPA), as it aligns perfectly with SvelteKit’s architecture and Tauri’s webview.

Auth0 dashboard showing SPA application creation

Configure Application Settings

To enable secure redirects for both desktop and web modes, we configure Auth0’s URLs to support the deep-linking plugin’s custom protocol and standard web callbacks.

In the application settings:

  • Set Allowed Callback URLs to:
    • agx://localhost/auth for the desktop app. This custom protocol is registered by the deep-linking plugin, allowing Auth0 to redirect back to the Tauri app after login.
    • http://localhost:1420 for web development (agx’s default development port).
    • The production URL https://agx.app for web mode.
  • Set Allowed Logout URLs to http://localhost:1420, https://agx.app and agx://localhost/auth.
  • Add Web Origins for:
    • http://localhost:1420 and the production domain https://agx.app for web mode.
    • tauri://localhost and https://tauri.localhost for the desktop app. These are necessary because Tauri’s webview sets the request origin to tauri://localhost or https://tauri.localhost, and Auth0 requires these origins to be explicitly allowed to prevent CORS errors during authentication.

The agx://localhost/auth URI must be added to Allowed Callback URLs to enable the deep-linking plugin to handle redirects back to the desktop app. Similarly, tauri://localhost and https://tauri.localhost in Web Origins ensure Auth0 recognizes the Tauri app’s origin for secure authentication.

Auth0 application settings with callback URLs and Web Origins

Set Up Tauri Plugins

To prepare Tauri, install the deep-linking and opener plugins using the Tauri CLI with these commands:

$ npm run tauri add deep-linking
$ npm run tauri add opener
Enter fullscreen mode Exit fullscreen mode

Run one command at a time to avoid issues. If agx is well-configured, the Tauri CLI will automatically add the necessary permissions and plugins to the app builder. If something goes wrong, check the requirements on the plugins’ documentation pages to resolve any hiccups.
In tauri.conf.json, configure the deep-linking plugin to register the agx:// protocol for desktop redirects.

{
  "plugins": {
    "deep-linking": {
      "desktop": {
        "schemes": ["agx"]
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Finally, add the Auth0 SPA SDK to the SvelteKit project with npm install @auth0/auth0-spa-js. These steps lay the foundation for Auth0’s Universal Login to work seamlessly across agx’s web and desktop modes, with the deep-linking plugin managing desktop redirects and the opener plugin handling browser-based logins.

Wiring Up Auth0: The Code

Here’s how we at Agnostic integrated Auth0’s SPA SDK with Universal Login, using Tauri’s deep-linking and opener plugins. To make the deep-linking plugin work for handling agx://localhost/auth redirects in desktop mode, build the app with npm run tauri build to create a production bundle, and add the --debug flag to enable devtools for easier debugging. The code below uses environment variables for Auth0 configuration, checks platform-specific login states, and manages redirects dynamically for both web and native modes.

// lib/auth.ts
import { type Auth0Client, createAuth0Client } from "@auth0/auth0-spa-js";
import { onOpenUrl } from "@tauri-apps/plugin-deep-link";
import { openUrl } from "@tauri-apps/plugin-opener";

if (!AUTH0_DOMAIN) throw new Error("AUTH0_DOMAIN is not defined");
if (!AUTH0_CLIENT_ID) throw new Error("AUTH0_CLIENT_ID is not defined");

let client: Auth0Client;
async function init() {
  client = await createAuth0Client({
    domain: AUTH0_DOMAIN,
    clientId: AUTH0_CLIENT_ID,
    authorizationParams: {
      redirect_uri: AUTH0_REDIRECT_URI || window.location.origin,
    },
    cacheLocation: "localstorage",
    useRefreshTokens: true,
  });
}

export async function checkLoginState() {
  await init();

  if (PLATFORM === "WEB") {
    if (
      window.location.search.includes("code=") &&
      window.location.search.includes("state=")
    ) {
      await client.handleRedirectCallback();
      window.history.replaceState({}, document.title, "/");
    }
  }

  if (PLATFORM === "NATIVE") {
    await onOpenUrl(async (urls) => {
      const url = urls
        .map((u) => new URL(u))
        .find((u) => u.searchParams.has("code") && u.searchParams.has("state"));

      if (url) {
        await client.handleRedirectCallback(url.toString());
      }
    });
  }
}

export async function isAuthenticated() {
  if (client) {
    return await client.isAuthenticated();
  }
  return false;
}

export async function getToken() {
  if (client) return await client.getTokenSilently();
}

export async function login() {
  if (client) {
    await client.loginWithRedirect({
      async openUrl(url) {
        if (PLATFORM === "WEB") {
          window.location.assign(url);
        }

        if (PLATFORM === "NATIVE") {
          await openUrl(url);
        }
      },
    });
  }
}

export async function logout() {
  if (client) {
    await client.logout({
      logoutParams: { returnTo: AUTH0_REDIRECT_URI || window.location.origin },
    });
  }
}
Enter fullscreen mode Exit fullscreen mode

This code relies on environment variables (AUTH0_DOMAIN, AUTH0_CLIENT_ID, AUTH0_REDIRECT_URI) to configure Auth0. The PLATFORM constant (WEB or NATIVE) determines whether to handle redirects via the browser or the deep-linking plugin’s onOpenUrl. For desktop mode, the opener plugin opens the Universal Login page in the default browser, and agx://localhost/auth serves as the redirect URI, caught by the deep-linking plugin. For web mode, it falls back to standard redirects using window.location.origin. The useRefreshTokens: true setting ensures secure token management for the SPA with Universal Login. The checkLoginState function handles callback logic and cleaning up the URL after authentication.

Wrapping Up

At Agnostic, we’ve integrated Auth0’s Universal Login with Tauri’s deep-linking and opener plugins to deliver secure, seamless authentication for agx across web and desktop. The deep-linking plugin handles redirects to agx://localhost/auth, while the opener plugin opens the Universal Login page in the default browser for desktop users. Including tauri://localhost and https://tauri.localhost as Web Origins in Auth0 eliminates CORS issues in Tauri’s webview. This setup creates a consistent experience, unlocking features like the AI Assistant. Want to dive into this implementation? Check out the agx repository and give it a try!

Top comments (3)

Collapse
 
dotallio profile image
Dotallio

I love how you handled deep-linking and CORS between web and desktop - so many teams trip on these details. Did you run into any gotchas with the Tauri plugins or discover any quirks you wish you'd known sooner?

Collapse
 
yannamsellem profile image
Yann Amsellem

Thanks for your comment! I’m glad you enjoyed the deep-linking and CORS approach—it was a fun challenge for agx’s web and desktop modes. With the Tauri plugins like deep-link and opener, I did hit a few gotchas. One thing I wish I’d known sooner was the need to explicitly add tauri://localhost and https://tauri.localhost as Web Origins in Auth0—missing that caused some CORS headaches early on until I figured out Tauri’s webview sets those origins by default.

Collapse
 
yoni_g_c80d405dbf9781040a profile image
Yoni G

Really clear thank you very much, i will try it