Skip to content

Instantly share code, notes, and snippets.

@farmisen
Created February 24, 2023 06:34
Show Gist options
  • Save farmisen/44569aa8ddfa09ae052243f930840bcf to your computer and use it in GitHub Desktop.
Save farmisen/44569aa8ddfa09ae052243f930840bcf to your computer and use it in GitHub Desktop.
tauri oauth example
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta
name="viewport"
content="initial-scale=1,width=device-width"
/>
<title>auth-init</title>
<script>
document.addEventListener("readystatechange", async (event) => {
if (document.readyState === "complete") {
const origin = __TAURI_METADATA__.__currentWindow.label;
switch (origin) {
case "auth-google":
await window.__TAURI_INVOKE__("google_auth_ready");
break;
case "auth-microsoft":
await window.__TAURI_INVOKE__("microsoft_auth_ready");
break;
}
}
});
</script>
</head>
<body></body>
</html>
[package]
edition = "2021"
name = "tauri-oauth-example"
version = "0.0.0"
description = "A Tauri App"
license = "Apache-2.0 WITH LLVM-exception"
[build-dependencies]
tauri-build = { version = "1.2", features = [] }
[dependencies]
oneshot = "0.1.5"
tauri = { version = "1.2", features = [] }
tokio = "1.24"
url = "2.3.1"
[features]
default = ["custom-protocol"]
custom-protocol = ["tauri/custom-protocol"]
[target.'cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd"))'.dependencies]
webkit2gtk = { version = "0.18", features = [
"v2_22",
], default-features = false }
[target.'cfg(any(target_os = "ios", target_os = "macos"))'.dependencies]
cacao = "0.3"
[target.'cfg(target_os = "windows")'.dependencies]
webview2-com = { version = "=0.19", default-features = false }
windows = { version = "=0.39", default-features = false }
# this revision is needed for the .on_navigation method in commands.rs
[patch.crates-io]
tauri = { git = "https://github.com/tauri-apps/tauri", rev = "3f35b45" }
use crate::Model;
use tauri::{AppHandle, Manager, Window};
use url::Url;
const GOOGLE_AUTH_URL: &str = "<some-auth-url>";
const GOOGLE_REDIRECT_URL: &str = "<some-redirect-url>";
const MICROSOFT_AUTH_URL: &str = "<some-auth-url>";
const MICROSOFT_REDIRECT_URL: &str = "<some-redirect-url>";
#[tauri::command]
pub async fn google_auth_flow(app: AppHandle) {
let (tx, rx) = std::sync::mpsc::channel::<url::Url>();
// NOTE: label determines which notifier is triggered from auth-init.html
let label = "auth-google";
let window = tauri::WindowBuilder::new(&app, label, tauri::WindowUrl::App("auth-init.html".into()))
.on_navigation(move |url: url::Url| {
let str = url.as_str();
if str.starts_with(GOOGLE_REDIRECT_URL) {
tx.send(url).unwrap();
return false;
}
true
})
.build()
.unwrap();
// wait for auth-init.html to notify ready
app.state::<Model>().notifiers.google_auth_ready.notified().await;
// <opportunity to clear cookies or cache on PlatformWebview before navigating>
// ...
// navigate to the auth url
let auth_url = Url::parse(GOOGLE_AUTH_URL).unwrap();
webview_navigate(&window, auth_url).unwrap();
// await the captured redirect url
let redirect_url = rx.recv().unwrap();
// do something interesting with "redirect_url"
}
#[tauri::command]
pub async fn microsoft_auth_flow(app: AppHandle) {
let (tx, rx) = std::sync::mpsc::channel::<url::Url>();
// NOTE: label determines which notifier is triggered from auth-init.html
let label = "auth-microsoft";
let window = tauri::WindowBuilder::new(&app, label, tauri::WindowUrl::App("auth-init.html".into()))
.on_navigation(move |url: url::Url| {
let str = url.as_str();
if str.starts_with(MICROSOFT_REDIRECT_URL) {
tx.send(url).unwrap();
return false;
}
true
})
.build()
.unwrap();
// wait for auth-init.html to notify ready
app.state::<Model>().notifiers.microsoft_auth_ready.notified().await;
// <opportunity to clear cookies or cache on PlatformWebview before navigating>
// ...
// navigate to the auth url
let auth_url = Url::parse(MICROSOFT_AUTH_URL).unwrap();
webview_navigate(&window, auth_url).unwrap();
// await the captured redirect url
let redirect_url = rx.recv().unwrap();
// do something interesting with "redirect_url"
}
#[tauri::command]
pub async fn google_auth_ready(app: AppHandle) {
app.state::<Model>().notifiers.google_auth_ready.notify_waiters();
}
#[tauri::command]
pub async fn microsoft_auth_ready(app: AppHandle) {
app.state::<Model>().notifiers.microsoft_auth_ready.notify_waiters();
}
pub type BoxError = Box<dyn std::error::Error + Send + Sync + 'static>;
pub type BoxResult<T> = Result<T, BoxError>;
#[cfg(any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "openbsd",
target_os = "netbsd"
))]
fn webview_navigate(window: &Window, url: Url) -> BoxResult<()> {
use webkit2gtk::WebViewExt;
window.with_webview(move |webview| {
let webview = webview.inner();
webview.load_uri(url.as_str());
})?;
Ok(())
}
#[cfg(target_os = "macos")]
fn webview_navigate(window: &Window, url: Url) -> BoxResult<()> {
use cacao::{
foundation::{id, NSString},
objc::*,
};
window
.with_webview(move |webview| unsafe {
let webview = webview.inner();
let string = NSString::new(url.as_str());
let url: id = msg_send![class!(NSURL), URLWithString: string];
let request: id = msg_send![class!(NSURLRequest), requestWithURL: url];
let _navigation: id = msg_send![webview, loadRequest: request];
})
.map_err(Into::into)
}
#[cfg(target_os = "windows")]
fn webview_navigate(window: &Window, url: Url) -> BoxResult<()> {
use tauri::window::PlatformWebview;
use webview2_com::Error::WindowsError;
use windows::core::HSTRING;
unsafe fn run(webview: PlatformWebview, url: Url) -> Result<(), wry::Error> {
let webview = webview.controller().CoreWebView2().map_err(WindowsError)?;
let url = &HSTRING::from(url.as_str());
webview.Navigate(url).map_err(WindowsError)?;
Ok(())
}
let (call_tx, call_rx) = oneshot::channel();
window
.with_webview(move |webview| unsafe {
let result = run(webview, url).map_err(Into::into);
call_tx.send(result).unwrap();
})
.map_err(Into::into)
.and(call_rx.recv().unwrap())
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta
name="viewport"
content="initial-scale=1,width=device-width"
/>
<title>auth-init</title>
<script>
function onClickGoogle() {
window.__TAURI_INVOKE__("google_auth_flow");
}
function onClickMicrosoft() {
window.__TAURI_INVOKE__("microsoft_auth_flow");
}
</script>
</head>
<body>
<h1>example</h1>
<div>
<button
type="button"
onclick="onClickGoogle()"
>
google auth
</button>
</div>
<div>
<button
type="button"
onclick="onClickMicrosoft()"
>
microsoft auth
</button>
</div>
</body>
</html>
#![cfg_attr(all(not(debug_assertions), target_os = "windows"), windows_subsystem = "windows")]
mod commands;
use std::sync::Arc;
use tauri::{AppHandle, Manager};
use tokio::sync::Notify;
#[derive(Clone, Default)]
pub struct Model {
pub notifiers: Notifiers,
}
#[derive(Clone, Default)]
pub struct Notifiers {
pub google_auth_ready: Arc<Notify>,
pub microsoft_auth_ready: Arc<Notify>,
}
fn main() {
tauri::Builder::default()
.manage(Model::default())
.invoke_handler(tauri::generate_handler![
commands::google_auth_flow,
commands::microsoft_auth_flow,
commands::google_auth_ready,
commands::microsoft_auth_ready,
])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment