OAuth Vendor Setup Guide (Desktop / Electron)
hosea uses OAuth 2.0 Authorization Code + PKCE for authenticating users with third-party services. The infrastructure is fully implemented — this document covers how to register your app with each vendor.
Hosea ships without organization-owned client IDs or shared client secrets.
Configure a service connector through the UI, or add your own quick-connect
registrations to <HOSEA_DATA_DIR>/built-in-oauth-apps.json (default ~/.hosea/).
The file is an array of BuiltInOAuthApp objects defined in
src/main/built-in-oauth-apps.ts; restart after changing it. For example:
[
{
"vendorId": "microsoft",
"displayName": "Microsoft",
"clientId": "YOUR_PUBLIC_CLIENT_ID",
"authTemplateId": "oauth-user",
"scopes": ["User.Read", "offline_access"],
"extraCredentials": { "tenantId": "common" }
}
]
Only use registrations you control. Desktop apps cannot keep embedded client secrets confidential; prefer supported public-client / PKCE flows. Quick-connect cards appear only for configured registrations. The backend login/proxy path is separate and disabled for now.
Redirect URI for all vendors: http://localhost:19876/oauth/callback
Architecture Overview
User clicks "Connect" in the app
→ App opens system browser → vendor OAuth consent page
→ User approves
→ Vendor redirects to http://localhost:19876/oauth/callback
→ App captures auth code, exchanges for tokens (PKCE)
→ Tokens stored locally (AES-256-GCM encrypted)
All flows use the existing VendorOAuthService + OAuthCallbackServer in apps/hosea/src/main/.
Tier 1: Full Marketplace + PKCE Support
Microsoft (Entra ID / Azure AD)
Register once → any M365 user can authenticate
| Field | Value |
|---|---|
| Portal | Azure App Registrations |
| Client Secret Needed | No (public client + PKCE) |
| Admin Consent Required | Only for OnlineMeetingTranscript.Read.All |
| Time to Go Live | Immediate |
Steps:
- Go to Azure Portal → App Registrations → "New registration"
- Name:
hosea - Supported account types: "Accounts in any organizational directory and personal Microsoft accounts" (multi-tenant + personal)
- Redirect URI: Platform = "Mobile and desktop applications", URI =
http://localhost:19876/oauth/callback
- Name:
- After creation, note the Application (client) ID — this is your
clientId - Go to "Authentication" tab:
- Under "Advanced settings", toggle "Allow public client flows" → Yes
- Confirm redirect URI is listed under "Mobile and desktop applications"
- Go to "API permissions" → Add Microsoft Graph delegated permissions (see scope table below)
- For admin-consent scopes: click "Grant admin consent for [tenant]" (requires Global Admin)
Do NOT create a client secret — the app is a public client using PKCE.
Connector config:
{
"name": "microsoft",
"vendor": "microsoft",
"auth": {
"type": "oauth",
"flow": "authorization_code",
"clientId": "<your-application-client-id>",
"authorizationUrl": "https://login.microsoftonline.com/common/oauth2/v2.0/authorize",
"tokenUrl": "https://login.microsoftonline.com/common/oauth2/v2.0/token",
"redirectUri": "http://localhost:19876/oauth/callback",
"scope": "openid profile offline_access Calendars.ReadWrite OnlineMeetings.Read OnlineMeetingTranscript.Read.All ChannelMessage.Send ChatMessage.Send Chat.ReadWrite Sites.ReadWrite.All Files.ReadWrite.All",
"usePKCE": true
}
}
Authorization URL variants:
/common/— any org + personal Microsoft accounts (broadest)/organizations/— work/school accounts only/consumers/— personal Microsoft accounts only/<tenant-id>/— single organization only
Microsoft Graph Permissions Reference
| Scope | Purpose | Consent | Notes |
|---|---|---|---|
openid |
OpenID Connect sign-in | User | Required for identity |
profile |
User's basic profile | User | Name, email |
offline_access |
Refresh tokens | User | Required for persistent sessions |
Calendars.ReadWrite |
Read/write calendar events | User | Create, update, delete meetings. Setting isOnlineMeeting: true creates Teams meetings without extra scopes. |
OnlineMeetings.Read |
List online meetings | User | Required to enumerate meetings before fetching transcripts |
OnlineMeetingTranscript.Read.All |
Read meeting transcripts | Admin | Returns .vtt or .docx format. Transcription must be enabled in Teams admin center. |
ChannelMessage.Send |
Send to Teams channels | User | POST to /teams/{id}/channels/{id}/messages. User must be a channel member. |
ChatMessage.Send |
Send to Teams chats | User | POST to /chats/{id}/messages. For existing chats only. |
Chat.ReadWrite |
Read/write Teams chats | User | Create new chats, read chat metadata and messages. |
Sites.ReadWrite.All |
SharePoint sites, lists, docs | User | Despite .All, delegated access respects SharePoint permissions. Covers search via /search/query. |
Files.ReadWrite.All |
OneDrive files + shared files | User | User's OneDrive + files shared with them. .All needed for shared files; without it only user's own OneDrive. |
Gotchas:
Sites.ReadWrite.AllandFiles.ReadWrite.Allsound broad but in delegated mode they only access what the signed-in user already has permission to see in SharePoint/OneDrive- For cross-service search (OneDrive + SharePoint + Teams files), use the
/search/queryendpoint — requires bothFiles.Read.AllandSites.Read.All - Reading channel messages (not just sending) requires
ChannelMessage.Read.All(admin consent) - Teams messaging endpoints are rate-limited more aggressively (~2 msg/sec per app per chat)
Google (Google Cloud / Workspace)
Register once → any Google user can authenticate
| Field | Value |
|---|---|
| Portal | Google Cloud Console → Credentials |
| Client Secret Needed | Yes (Google requires it even for desktop apps, but it's not truly secret) |
| Verification Required | Yes, for sensitive scopes (Gmail, Drive, Calendar) |
| Time to Go Live | Immediate for testing; days-weeks for production verification |
Steps:
- Go to Google Cloud Console → Create project (or use existing)
- Enable required APIs: Gmail API, Google Calendar API, Google Drive API, etc.
- Go to Credentials → "Create Credentials" → "OAuth client ID"
- Application type: "Desktop app"
- Name:
hosea
- Download the JSON — note Client ID and Client Secret
- Go to "OAuth consent screen":
- User Type: "External"
- Fill in app name, support email, logo
- Add scopes for each API
- Add test users (required while in "Testing" status)
- To go to production: click "Publish App" (triggers Google verification for sensitive scopes)
Connector config:
{
"name": "google",
"vendor": "google",
"auth": {
"type": "oauth",
"flow": "authorization_code",
"clientId": "<your-client-id>.apps.googleusercontent.com",
"clientSecret": "<your-client-secret>",
"authorizationUrl": "https://accounts.google.com/o/oauth2/v2/auth",
"tokenUrl": "https://oauth2.googleapis.com/token",
"redirectUri": "http://localhost:19876/oauth/callback",
"scope": "openid profile email https://www.googleapis.com/auth/gmail.readonly https://www.googleapis.com/auth/calendar https://www.googleapis.com/auth/drive",
"usePKCE": true
}
}
Gotchas:
- While in "Testing" mode, only listed test users can authenticate (max 100 users)
- Sensitive scopes require Google's verification process (privacy policy, homepage, demo video)
- Add
access_type=offlineto get refresh tokens (may need extra auth URL param) - Google issues a client secret for desktop apps but it's considered non-confidential
GitHub
Register once → any GitHub user can authenticate
| Field | Value |
|---|---|
| Portal | GitHub → Settings → Developer settings → GitHub Apps |
| Client Secret Needed | Yes |
| Verification Required | None |
| Time to Go Live | Immediate |
Steps:
- Go to GitHub Settings → Developer settings → GitHub Apps → "New GitHub App"
- App name:
hosea - Homepage URL: your website
- Callback URL:
http://localhost:19876/oauth/callback - Check "Request user authorization (OAuth) during installation"
- Uncheck "Active" under Webhook (unless needed)
- App name:
- Set Permissions (per resource):
- Repository: Contents (read/write), Issues (read/write), Pull requests (read/write), etc.
- Account: Email addresses (read)
- Where can this app be installed: "Any account"
- After creation: note Client ID, generate a Client Secret
Connector config:
{
"name": "github",
"vendor": "github",
"auth": {
"type": "oauth",
"flow": "authorization_code",
"clientId": "<your-client-id>",
"clientSecret": "<your-client-secret>",
"authorizationUrl": "https://github.com/login/oauth/authorize",
"tokenUrl": "https://github.com/login/oauth/access_token",
"redirectUri": "http://localhost:19876/oauth/callback",
"scope": "repo read:user user:email",
"usePKCE": true
}
}
Gotchas:
- GitHub tokens don't expire by default. Enable "Expire user authorization tokens" in app settings for refresh token support.
- GitHub App (preferred) vs OAuth App: GitHub Apps have granular per-repo permissions and can be installed per-org
- For Marketplace listing: go to app settings → "List in Marketplace"
Salesforce
Register once → any Salesforce org can authenticate
| Field | Value |
|---|---|
| Portal | Setup → Apps → App Manager → New Connected App |
| Client Secret Needed | Optional with PKCE (include for refresh token reliability) |
| Verification Required | AppExchange = months; direct OAuth = none |
| Time to Go Live | Immediate (2-10 min propagation) |
Steps:
- Log into a Salesforce org → Setup → Apps → App Manager → "New Connected App"
- Connected App Name:
hosea - Contact Email: your email
- Connected App Name:
- Under "API (Enable OAuth Settings)":
- Check "Enable OAuth Settings"
- Callback URL:
http://localhost:19876/oauth/callback - Selected OAuth Scopes:
api,refresh_token,offline_access - Check "Require Proof Key for Code Exchange (PKCE)"
- Check "Enable Authorization Code and Credentials Flow"
- Under "OAuth Policies":
- Permitted Users: "All users may self-authorize"
- Save and wait 2-10 minutes for propagation
- Note Consumer Key (= clientId) and Consumer Secret
Connector config:
{
"name": "salesforce",
"vendor": "salesforce",
"auth": {
"type": "oauth",
"flow": "authorization_code",
"clientId": "<consumer-key>",
"clientSecret": "<consumer-secret>",
"authorizationUrl": "https://login.salesforce.com/services/oauth2/authorize",
"tokenUrl": "https://login.salesforce.com/services/oauth2/token",
"redirectUri": "http://localhost:19876/oauth/callback",
"scope": "api refresh_token",
"usePKCE": true
}
}
URL variants:
https://login.salesforce.com— production orgshttps://test.salesforce.com— sandbox orgs
Gotchas:
- Salesforce fully supports PKCE without client_secret (true public client)
- Refresh tokens are long-lived by default
- AppExchange listing requires Salesforce Security Review (extensive process)
Tier 1: Require Backend Token Proxy (No PKCE)
These vendors require client_secret for token exchange and do NOT support PKCE for public clients. For desktop apps, you need a lightweight backend proxy to exchange the auth code.
Slack
| Field | Value |
|---|---|
| Portal | Slack API → Your Apps |
| Client Secret Needed | Yes (required at token exchange) |
| PKCE Support | No |
Steps:
- Go to api.slack.com/apps → "Create New App" → "From scratch"
- Set Redirect URL:
http://localhost:19876/oauth/callback - Under "OAuth & Permissions": add Bot Token Scopes and User Token Scopes as needed
- Note Client ID and Client Secret
Requires backend proxy for token exchange. The desktop app sends the auth code to your backend, which exchanges it using the client secret.
HubSpot
| Field | Value |
|---|---|
| Portal | HubSpot Developer Portal |
| Client Secret Needed | Yes |
| PKCE Support | No |
Steps:
- Go to HubSpot Developer Portal → Create app
- Under "Auth": set Redirect URL to
http://localhost:19876/oauth/callback - Select required scopes
- Note Client ID and Client Secret
Requires backend proxy for token exchange.
Shopify
| Field | Value |
|---|---|
| Portal | Shopify Partners |
| Client Secret Needed | Yes |
| PKCE Support | No |
| Special Requirement | Redirect URI must be HTTPS |
Requires backend proxy — Shopify mandates HTTPS redirect URIs, so the entire OAuth flow must go through your server.
Tier 2: OAuth App Registration (Same PKCE Pattern)
These vendors support the same Authorization Code + PKCE pattern. Register using the same redirect URI (http://localhost:19876/oauth/callback).
| Vendor | Portal | Secret Required | PKCE | Admin Consent |
|---|---|---|---|---|
| Jira / Confluence | Atlassian Developer Console | Yes | Yes (3LO) | No |
| Linear | linear.app/developers | Yes | Yes | No |
| Asana | Asana Developer Console | Yes | Yes | No |
| Notion | notion.so/my-integrations | Yes | No | No |
| Airtable | Airtable Developer Hub | Yes | Yes | No |
| Dropbox | dropbox.com/developers | Yes | Yes | No |
| Box | Box Developer Console | Yes | Yes | No |
| Stripe | Stripe Connect | Yes | No | No |
| QuickBooks | Intuit Developer Portal | Yes | Yes | No |
| Discord | discord.com/developers | Yes | Yes | No |
| GitLab | GitLab Settings → Applications | Yes | Yes | No |
| Bitbucket | Bitbucket Settings → OAuth consumers | Yes | Yes | No |
| Zendesk | Zendesk Developer Portal | Yes | Yes | No |
| Intercom | Intercom Developer Hub | Yes | Yes | No |
| Mailchimp | Mailchimp Developer Portal | Yes | No | No |
| PagerDuty | PagerDuty Developer Platform | Yes | Yes | No |
| Pipedrive | Pipedrive Developer Portal | Yes | Yes | No |
Tier 3: API Key Only (No OAuth)
These services use static API keys. Users enter their key directly in the app's connector settings.
| Vendor | Auth Model |
|---|---|
| Telegram | Bot Token via @BotFather |
| SendGrid | API Key |
| Postmark | Server Token |
| Datadog | API Key + App Key |
| AWS | IAM Access Key + Secret |
| Twilio | Account SID + Auth Token |
| Serper / Brave Search / Tavily / RapidAPI | API Key |
| ZenRows | API Key |
Token Storage
hosea stores all OAuth tokens locally using AES-256-GCM encryption:
- Location:
~/.oneringai/oauth/tokens/(file permissions 0o600) - Encryption key: Set
OAUTH_ENCRYPTION_KEYenv var for persistence across restarts - Filenames: SHA256-hashed (no sensitive data in filesystem)
- Refresh: Automatic refresh 5 minutes before expiry