Skip to main content

tauri_plugin_oauth_session/
lib.rs

1//! Drive OAuth flows through ASWebAuthenticationSession on iOS and Chrome
2//! Custom Tabs on Android.
3//!
4//! On both platforms the plugin opens an in-app browser session, intercepts
5//! the redirect to the configured callback scheme, and resolves with the full
6//! callback URL — replacing the older "open external browser, poll backend"
7//! pattern.
8//!
9//! Platform asymmetry: `prefersEphemeralSession` is honored on iOS but has no
10//! Android equivalent — Custom Tabs always shares cookies with the user's
11//! default browser.
12
13#![cfg_attr(not(mobile), allow(dead_code))]
14
15use serde::de::DeserializeOwned;
16use tauri::{
17    AppHandle, Manager, Runtime,
18    plugin::{Builder, PluginApi, TauriPlugin},
19};
20
21mod commands;
22mod error;
23mod models;
24
25pub use error::{Error, Result};
26pub use models::{AuthenticateOptions, AuthenticateResponse};
27
28#[cfg(target_os = "ios")]
29tauri::ios_plugin_binding!(init_plugin_oauth_session);
30
31#[cfg(target_os = "android")]
32const PLUGIN_IDENTIFIER: &str = "app.tauri.oauth_session";
33
34/// Access to the OAuth session APIs.
35pub struct OAuthSession<R: Runtime>(OAuthSessionImpl<R>);
36
37#[cfg(mobile)]
38type OAuthSessionImpl<R> = tauri::plugin::PluginHandle<R>;
39
40// `PhantomData<fn() -> R>` is unconditionally `Send + Sync`, which is what
41// Tauri's `Manager::manage` requires.
42#[cfg(not(mobile))]
43type OAuthSessionImpl<R> = std::marker::PhantomData<fn() -> R>;
44
45impl<R: Runtime> OAuthSession<R> {
46    /// Open the platform-native authentication browser and resolve with the
47    /// redirect URL once the system intercepts the callback scheme.
48    pub async fn authenticate(
49        &self,
50        options: AuthenticateOptions,
51    ) -> Result<AuthenticateResponse> {
52        #[cfg(mobile)]
53        {
54            let response: AuthenticateResponse =
55                self.0.run_mobile_plugin("authenticate", options)?;
56            Ok(response)
57        }
58        #[cfg(not(mobile))]
59        {
60            let _ = options;
61            Err(Error::UnsupportedPlatform)
62        }
63    }
64}
65
66pub trait OAuthSessionExt<R: Runtime> {
67    fn oauth_session(&self) -> &OAuthSession<R>;
68}
69
70impl<R: Runtime, T: Manager<R>> OAuthSessionExt<R> for T {
71    fn oauth_session(&self) -> &OAuthSession<R> {
72        self.state::<OAuthSession<R>>().inner()
73    }
74}
75
76fn init_oauth_session<R: Runtime, C: DeserializeOwned>(
77    _app: &AppHandle<R>,
78    _api: PluginApi<R, C>,
79) -> Result<OAuthSession<R>> {
80    #[cfg(target_os = "ios")]
81    {
82        let handle = _api.register_ios_plugin(init_plugin_oauth_session)?;
83        Ok(OAuthSession(handle))
84    }
85    #[cfg(target_os = "android")]
86    {
87        let handle = _api.register_android_plugin(PLUGIN_IDENTIFIER, "OAuthSessionPlugin")?;
88        Ok(OAuthSession(handle))
89    }
90    #[cfg(not(mobile))]
91    {
92        Ok(OAuthSession(std::marker::PhantomData))
93    }
94}
95
96pub fn init<R: Runtime>() -> TauriPlugin<R> {
97    Builder::new("oauth-session")
98        .invoke_handler(tauri::generate_handler![commands::authenticate])
99        .setup(|app, api| {
100            let plugin = init_oauth_session(app, api)?;
101            app.manage(plugin);
102            Ok(())
103        })
104        .build()
105}