How to Integrate with OAuth Providers like Google and Microsoft in Thunderbolt
Thunderbolt provides a provider-agnostic OAuth layer that unifies Google and Microsoft authentication through adapter modules, a core engine, and a React hook that handles PKCE, state management, and cross-platform redirects automatically.
The Thunderbird Thunderbolt codebase implements a modular OAuth architecture that abstracts provider differences while preserving platform-specific optimizations for web, mobile, and desktop environments. Whether you are building a Tauri-based desktop client or a browser-based interface, the same useOAuthConnect hook orchestrates the complete flow from authorization to credential storage.
Architecture Overview
Thunderbolt’s OAuth implementation is organized into three distinct layers that separate provider logic from platform concerns:
| Layer | Responsibility | Key Files |
|---|---|---|
| Provider-specific adapters | Retrieve provider config, build the auth URL, exchange the authorization code for tokens, and fetch user info. | src/integrations/google/auth.ts & src/integrations/microsoft/auth.ts |
| Core OAuth engine | Defines common types, delegates to the adapters, and decides which redirect strategy to use (web → redirect, Tauri → loopback/server). | src/lib/auth.ts, src/lib/oauth-redirect.ts, src/lib/oauth-loopback.ts |
| UI hook | Provides a React hook (useOAuthConnect) that UI components can call to start a flow, handle callbacks, store credentials, and expose loading / error state. |
src/hooks/use-oauth-connect.ts |
Core OAuth Types and Delegation
The src/lib/auth.ts file establishes the provider contract through TypeScript unions and wrapper functions that delegate to the concrete adapters.
// src/lib/auth.ts
export type OAuthProvider = 'google' | 'microsoft'
export type OAuthConfig = { clientId: string; redirectUri: string; scope: string }
export type OAuthTokens = {
access_token: string;
refresh_token?: string;
expires_in: number;
token_type: string
}
The core library re-exports adapter methods through a single providers registry:
export const getOAuthConfig = async (httpClient, provider) =>
providers[provider].getOAuthConfig(httpClient)
export const buildAuthUrl = async (httpClient, provider, state, codeChallenge, redirectUri?) =>
providers[provider].buildAuthUrl(httpClient, state, codeChallenge, redirectUri)
Provider-Specific Adapters
Each OAuth provider implements four standardized functions: getOAuthConfig, buildAuthUrl, exchangeCodeForTokens, and getUserInfo.
Google Integration
The src/integrations/google/auth.ts module handles Google-specific endpoints:
- Config retrieval – Fetches dynamic configuration from the backend endpoint
auth/google/configand merges it with the platform-specific redirect URI determined bygetOAuthRedirectUri. - Authorization URL – Constructs
https://accounts.google.com/o/oauth2/v2/authwith PKCE code challenge parameters. - Token exchange – POSTs the authorization code to the backend endpoint
auth/google/exchange. - UserInfo – Calls the Google user-info endpoint to retrieve profile data.
Microsoft Integration
The src/integrations/microsoft/auth.ts file mirrors the Google structure but targets Microsoft Entra ID (formerly Azure AD) endpoints:
- Config – Fetches from
auth/microsoft/configand uses Microsoft-specific client registration. - Authorization URL – Points to
https://login.microsoftonline.com/.../oauth2/v2.0/authorizewith PKCE. - Token exchange – POSTs to
auth/microsoft/exchange. - UserInfo – Queries the Microsoft Graph API for profile details.
Both adapters expose identical signatures, allowing the core engine to treat them interchangeably.
Redirect Strategies and Platform Detection
The src/lib/oauth-redirect.ts module determines the appropriate callback URL based on the runtime environment:
if (!isTauri()) // Web → same origin callback
return window.location.origin + '/oauth/callback'
if (isMobile()) // Mobile apps – App/Universal Link
return 'https://thunderbolt.io/oauth/callback'
return window.location.origin + '/oauth-callback.html' // Desktop fallback
This logic ensures that web applications receive same-origin redirects, mobile deep links trigger the native app, and desktop clients use a local HTML file or loopback server.
Desktop Loopback Server
For Tauri desktop environments where remote redirects are unreliable, Thunderbolt spins up a local HTTP server via the startOAuthFlowLoopback function in src/lib/oauth-loopback.ts.
The flow proceeds as follows:
-
Server initialization – Invokes the Rust command
start_oauth_serverto bind to a random available port:const port = await invoke<number>('start_oauth_server') const redirectUri = `http://localhost:${port}` -
PKCE generation – Creates a
codeVerifierandcodeChallengefor the authorization request. -
Event registration – Sets up a Tauri event listener for
"oauth-callback"before opening the browser to prevent race conditions. -
Browser launch – Opens the system browser with the authorization URL constructed via
buildAuthUrl. -
Callback handling – The local server receives the OAuth callback, validates the
stateparameter, and exchanges the authorization code for tokens usingexchangeCodeForTokens. -
Resolution – Returns
{ tokens, userInfo }to the caller ornullif the 5-minute timeout expires.
React Integration with useOAuthConnect
The src/hooks/use-oauth-connect.ts hook provides the primary interface for UI components. It abstracts platform detection, state management, and credential persistence.
const { connect, processCallback, isConnecting, error, clearError } = useOAuthConnect({
connectingKey: 'google',
onSuccess: () => console.log('Connected'),
})
The hook handles three distinct platform paths:
- Tauri Desktop – Invokes
startOAuthFlowLoopback, then stores credentials viaupdateSettingson success. - Tauri Mobile – Builds the auth URL, persists temporary state to SQLite (
oauth_state,oauth_provider), and opens the system browser viaopenUrl. The deep-link listener later invokesprocessCallbackto validate state and exchange codes. - Web – Calls
redirectOAuthFlowto navigate the browser to the provider’s consent screen. Upon return to/oauth/callback, the route component callsprocessCallbackwith the URL parameters.
The processCallback function (lines 59-85) validates the stored state against the callback parameter, exchanges the authorization code, saves the credentials to the settings table, clears the temporary SQLite entries, and triggers the onSuccess callback.
Storing Credentials
Upon successful authentication, saveCredentials writes a JSON blob to the SQLite settings table:
await updateSettings(db, {
[`integrations_${provider}_credentials`]: JSON.stringify(credentials),
[`integrations_${provider}_is_enabled`]: 'true',
})
The stored object contains the access token, optional refresh token, expiry timestamp, and a minimal profile including email, name, and picture.
Summary
- Thunderbolt uses a three-layer architecture: provider adapters for Google/Microsoft, a core engine for platform abstraction, and a React hook for UI integration.
- Provider adapters in
src/integrations/{google,microsoft}/auth.tsimplement four standardized functions:getOAuthConfig,buildAuthUrl,exchangeCodeForTokens, andgetUserInfo. - Platform detection in
src/lib/oauth-redirect.tsautomatically selects appropriate redirect URIs for web, mobile deep links, or desktop loopback servers. - Desktop Tauri clients use
src/lib/oauth-loopback.tsto spawn a local HTTP server, eliminating the need for remote callback URLs. - The
useOAuthConnecthook insrc/hooks/use-oauth-connect.tsmanages the entire flow including PKCE generation, state validation, token exchange, and credential persistence.
Frequently Asked Questions
How does Thunderbolt handle PKCE for OAuth security?
Thunderbolt generates a PKCE code verifier and challenge within the startOAuthFlowLoopback function in src/lib/oauth-loopback.ts. The challenge is sent to the provider's authorization endpoint, while the verifier is retained locally and sent during the token exchange. This prevents authorization code interception attacks, particularly critical for the desktop loopback server scenario where multiple applications could potentially bind to the same port.
Can I add a new OAuth provider without modifying the core library?
Yes. To add a new provider, create a new adapter module in src/integrations/{provider}/auth.ts that exports the four required functions: getOAuthConfig, buildAuthUrl, exchangeCodeForTokens, and getUserInfo. Then extend the OAuthProvider union type in src/lib/auth.ts to include your new provider string. The useOAuthConnect hook and redirect logic will automatically work with your new provider without additional changes.
What is the difference between web and desktop OAuth flows in Thunderbolt?
The web flow in Thunderbolt uses redirectOAuthFlow to navigate the browser to the provider's consent screen, then handles the callback at /oauth/callback via the processCallback function. In contrast, the desktop Tauri flow uses startOAuthFlowLoopback in src/lib/oauth-loopback.ts to spawn a local HTTP server on a random port, allowing the OAuth provider to redirect to http://localhost:{port}. The desktop flow also uses Tauri event listeners to capture the callback without requiring a remote redirect URL.
How are OAuth credentials stored and secured in Thunderbolt?
Upon successful authentication, saveCredentials in src/hooks/use-oauth-connect.ts stores credentials in the SQLite settings table using the updateSettings function. The credentials are serialized as JSON and stored under keys formatted as integrations_${provider}_credentials and integrations_${provider}_is_enabled. The actual token storage leverages the application's existing SQLite database, which on desktop platforms is encrypted by the host OS keychain or credential manager via Tauri's secure storage plugins, ensuring refresh tokens remain protected at rest.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →