Appearance
OAuth providers
Different providers behave differently. This page is the reference for each provider's redirect placement, scope set, token storage, and refresh story.
At a glance
| Provider | Family | Redirect lands | Token stored | Refresh | Implemented |
|---|---|---|---|---|---|
| OIDC sign-in | HiveCast | HiveCast cloud | session | yes (GoogleOidcLoginProvider) | |
| GitHub | OIDC sign-in | HiveCast | HiveCast cloud | session | target state |
| Enterprise SSO (Okta, Entra) | OIDC sign-in | HiveCast | HiveCast cloud | session | target state |
| Anthropic | Provider OAuth | local-loopback | Device Factotum | provider refresh token | yes (manual import + paste; full OAuth target state) |
| Codex (OpenAI) | Provider OAuth | local-loopback | Device Factotum | provider refresh token | yes (import from ~/.codex/auth.json) |
| OpenAI API key | Provider API key | n/a | Device Factotum | n/a | yes (manual paste) |
| Ollama | Local service | n/a | Device Factotum (baseUrl only) | n/a | yes |
Google (HiveCast sign-in)
Source: projects/matrix-3/packages/system-auth/src/google-oidc.ts.
Configuration:
ts
interface IGoogleOidcLoginConfig {
clientId: string;
clientSecret: string;
redirectUri: string; // https://hivecast.ai/api/auth/callback/google
}Flow:
auth.google.loginbuilds the authorize URL with PKCE (generateCodeVerifier()+generateCodeChallenge()) and stores{ verifier, redirect }in the pending-login store.- Browser → Google → callback to
<hivecast>/api/auth/callback/google. auth.google.callbackcallsexchangeCodeForTokens(code, verifier)to getid_token+access_token.verifyGoogleIdToken(id_token)checks signature against Google's JWKS,iss,aud(== configuredclientId),exp,email_verified.HostPrincipalStore.findOrCreate('https://accounts.google.com', sub, claims)returns the principal record (created on first sign-in).HostSessionService.createSession(principalId, { email, displayName })mints the cookie.
The Google access_token and refresh_token (if any) are NOT persisted. Only the verified claims map to a principalId. HiveCast does not need to call Google APIs after sign-in.
GitHub / Enterprise SSO (target state)
Same shape as Google: pre-registered redirect at HiveCast, OIDC token verified, principal lookup by (iss, sub). Implementation parallels GoogleOidcLoginProvider. Today only Google is wired.
Anthropic (provider OAuth)
Anthropic OAuth uses local-loopback redirect:
http://localhost:<port>/callbackThis is registered with Anthropic per app. Tokens issued belong on the user's machine. Storage:
ts
{
type: 'oauth',
accessToken: '<token>',
refreshToken: '<token>',
expiresAt: <unix-ms>,
scopes: [...],
source: 'oauth' | 'import-claude',
}Where it lives: <matrix-home>/credentials.json, written by system.factotum's credential.store op. The import-claude source indicates it was imported from ~/.claude/.credentials.json automatically by credential.import-detected + credential.import.
credential.refresh for Anthropic today is a partial implementation (returns the existing token's expiry; full refresh-token round-trip is target state — see source in system-factotum/dist/index.js:479-490).
Codex (OpenAI)
Same model as Anthropic. The Codex CLI writes ~/.codex/auth.json after its own OAuth, and Factotum's CredentialImporter detects:
ts
// projects/matrix-3/packages/system-factotum/dist/index.js:124-138
const codexPath = path.join(this._homeDir, '.codex', 'auth.json');
if (fs.existsSync(codexPath)) {
const raw = JSON.parse(fs.readFileSync(codexPath, 'utf8'));
if (raw?.token || raw?.accessToken) {
detected.push({
source: 'codex-cli',
provider: 'codex',
description: 'Codex CLI OAuth credentials (~/.codex/auth.json)',
hasToken: true
});
}
}credential.import-detected returns this. credential.import({ source: 'codex-cli', provider: 'codex' }) actually copies it into Factotum's store.
The Codex token is treated as oauth (it has accessToken, refreshToken); the Factotum store handles it the same as Anthropic's.
OpenAI API key
If the user has an OPENAI_API_KEY env var or pastes a key directly, Factotum stores:
ts
{
type: 'api-key',
apiKey: '<key>',
source: 'env' | 'manual',
}credential.validate for OpenAI hits https://api.openai.com/v1/models with the key as Authorization: Bearer <key>; success means the key is valid.
Ollama
Local-only. Factotum stores:
ts
{
type: 'local',
baseUrl: 'http://localhost:11434',
source: 'env' | 'manual',
}No tokens. credential.lease returns a lease that credential.materialize converts to { baseUrl: '...', headers: {} }.
Multiple providers, single Factotum
Each Device's Factotum is a single actor with a single per-Device store. Multiple provider credentials co-exist as keys in the credentials.json providers map:
json
{
"providers": {
"anthropic": { "type": "oauth", "accessToken": "..." },
"codex": { "type": "oauth", "accessToken": "..." },
"openai": { "type": "api-key", "apiKey": "..." },
"ollama": { "type": "local", "baseUrl": "http://localhost:11434" }
}
}credential.list returns metadata for all of them (no secrets).
Cross-Device behaviour
Factotum's store does NOT replicate across Devices. If you pair a second Device, you re-do the per-provider OAuth on that Device. This is intentional — see Why hosts do not receive raw OAuth tokens. A target-state "secret sync" feature would need explicit user opt-in, end-to-end encryption between Devices, and a clear threat model that keeps the cloud out of the encryption key path.
See also
- HiveCast account login — Google detail.
- Device flow — Host pairing auth.
- Secrets / Factotum — the local credentials actor.
- Secrets / no-raw-oauth-tokens-on-local-host — the boundary rule.
Source:
projects/matrix-3/packages/system-auth/src/google-oidc.ts(Google OIDC);projects/matrix-3/packages/system-factotum/dist/index.js(Factotum store,CredentialImporter).