OAuth and OpenID Connect setup¶
This guide configures authentication and API-key connections for the Subconscious FastAPI server. Google and GitHub are sign-in/sign-up identities. Anthropic, DeepSeek, and Ollama use API keys and can never authenticate a user.
Provider roles¶
| Provider | Protocol | Sign in/up | Connect to an existing account |
|---|---|---|---|
| OpenID Connect over OAuth 2.0 | Yes | Yes | |
| GitHub | OAuth 2.0 | Yes | Yes |
| Anthropic, DeepSeek, Ollama | API key | No | Yes |
The server hides an OAuth provider when both credentials are absent and refuses to start when only its client ID or client secret is configured.
1. Choose canonical URLs¶
Set the public URLs before creating provider credentials. APP_BASE_URL determines
all OAuth callback URLs; APP_UI_BASE_URL is where successful browser flows return.
Production and staging must use HTTPS.
APP_ENV=production
APP_BASE_URL=https://api.subconscious.chat
APP_UI_BASE_URL=https://app.subconscious.chat
WEB_BASE_URL=https://subconscious.chat
DOCS_BASE_URL=https://docs.subconscious.chat
CORS_ALLOWED_ORIGINS=https://app.subconscious.chat,https://subconscious.chat
TRUSTED_HOSTS=api.subconscious.chat,subconscious.chat,app.subconscious.chat
Use these exact provider callback URLs for the example domain:
| Provider | Callback URL |
|---|---|
https://api.subconscious.chat/auth/google/callback |
|
| GitHub | https://api.subconscious.chat/auth/github/callback |
For local development with APP_BASE_URL=http://localhost:5050, use the equivalent
http://localhost:5050/auth/{provider}/callback. If a provider does not accept a
local HTTP callback, use a trusted HTTPS development tunnel and set APP_BASE_URL
to that tunnel origin.
2. Configure server secrets¶
Generate independent JWT and Fernet keys. Do not reuse OAuth client secrets for application signing or encryption.
python -c "import secrets; print(secrets.token_hex(32))"
python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"
Store the results and provider credentials in your deployment secret manager:
JWT_SECRET_KEY=replace-with-generated-random-value
TOKEN_ENCRYPTION_KEY=replace-with-generated-fernet-key
GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=
GITHUB_CLIENT_ID=
GITHUB_CLIENT_SECRET=
Never place production values in .env.example, source control, image layers,
workflow files, screenshots, or support logs. Restart the application after changing
credentials. Changing a provider client ID also requires updating its provider-console
callback configuration.
3. Google OpenID Connect¶
- Create or select a project in Google Cloud Console.
- Configure the OAuth consent screen. Choose internal access only if every user is in the same Google Workspace organization; otherwise configure external access and add test users while the app remains in testing mode.
- Create an OAuth client with application type Web application.
- Add the exact Google callback URL from the table above under authorized redirect URIs. This server performs the authorization-code exchange; it does not require a browser JavaScript origin for login.
- Copy the generated values into
GOOGLE_CLIENT_IDandGOOGLE_CLIENT_SECRET. - Ensure the consent screen permits the requested
openid,email, andprofilescopes. Complete Google's publication or verification steps if your audience requires them.
The server validates Google's ID-token signature using Google's published keys and requires RS256, the Google issuer, your configured client ID as audience, expiration, a stable subject, and the per-flow nonce. A verified email is additionally required when a Google identity first establishes a local sign-in account; subsequent login resolves the already-linked stable provider subject.
Official references: Google OpenID Connect and web-server OAuth.
4. GitHub OAuth¶
- In GitHub, open Settings → Developer settings → OAuth Apps and create an OAuth App.
- Set the homepage URL to your public web application.
- Set the authorization callback URL to the exact GitHub callback URL above. Use a separate OAuth app per environment unless your GitHub configuration explicitly supports every required callback.
- Copy the client ID and generate a client secret. Store them as
GITHUB_CLIENT_IDandGITHUB_CLIENT_SECRET.
The server requests read:user and user:email. It does not trust the public email
field from the profile endpoint. Login succeeds only when GitHub's email endpoint
returns a structurally valid email marked as verified; the primary verified email is
preferred, with another verified email used as a fallback.
Official reference: Creating a GitHub OAuth app.
5. Browser integration¶
To sign in or sign up, navigate the browser to one of these endpoints:
GET /auth/google/login
GET /auth/github/login
The API redirects to the provider. After a valid callback, the server sets an
HTTP-only refresh cookie scoped to /auth and redirects to:
{APP_UI_BASE_URL}?auth=success
To connect Google or GitHub to an already authenticated account:
- Send
GET /auth/{provider}/login?mode=linkwith the Subconscious bearer access token. - Preserve response cookies and open the returned JSON
urlin the same browser. - The provider callback returns to
{APP_UI_BASE_URL}?connected={provider}.
Cross-origin frontend requests must use credentials so the browser-binding cookie is stored and returned:
const response = await fetch(
"https://api.subconscious.chat/auth/google/login?mode=link",
{
headers: { Authorization: `Bearer ${accessToken}` },
credentials: "include",
},
);
const { url } = await response.json();
window.location.assign(url);
Do not copy a link URL to another browser profile. Web login and link states are bound to an HTTP-only, same-site browser cookie and expire after ten minutes. State is single-use. In staging and production these cookies require HTTPS.
Useful endpoints:
| Action | Endpoint |
|---|---|
| List configured and connected providers | GET /auth/providers |
| Disconnect a provider | DELETE /auth/{provider}/disconnect |
| Current user and connections | GET /auth/me |
An account cannot disconnect its final sign-in method unless it has a password or a second Google/GitHub login identity.
6. Native desktop/mobile login with PKCE¶
Native linking is not supported; native Google/GitHub login uses an exact loopback HTTP redirect and S256 PKCE. Generate a random verifier of 43–128 RFC 7636 characters, then calculate:
challenge = BASE64URL_NO_PADDING(SHA256(ASCII(code_verifier)))
Start login with URL-encoded parameters:
GET /auth/google/login?native=true
&redirect_to=http://127.0.0.1:49152/callback
&code_challenge={43-character-challenge}
&code_challenge_method=S256
The JSON response contains the provider authorization url and state. Open that URL
in the system browser. The configured provider callback remains the server callback;
after validation, the server redirects the browser to the exact loopback URL with a
short-lived, one-time code.
Exchange the code from the native application:
POST /auth/native/token
Content-Type: application/json
{
"code": "one-time-code-from-loopback",
"code_verifier": "original-pkce-verifier"
}
The response includes an access token and an opaque refresh token. Store the refresh
token in the operating system credential vault. Never put Subconscious access or
refresh tokens in a URL. Loopback redirects must use http, an explicit port, and
exactly 127.0.0.1, localhost, or ::1; fragments and embedded credentials are
rejected.
7. API-key providers¶
Connect Anthropic, DeepSeek, or Ollama only after authenticating to Subconscious:
POST /auth/apikey
Authorization: Bearer {subconscious-access-token}
Content-Type: application/json
{
"provider_slug": "anthropic",
"api_key": "provider-api-key"
}
The key is Fernet-encrypted at rest and only a masked hint is returned. Disconnect it
with DELETE /auth/{provider}/disconnect. API-key providers cannot sign in.
8. Deployment checklist¶
- Register callbacks from
APP_BASE_URL, notAPP_UI_BASE_URL. - Match callback scheme, host, port, path, and trailing-slash behavior exactly.
- Use HTTPS for every non-loopback environment.
- Configure both variables for each enabled provider; leave both absent to disable it.
- Add API and web origins to
TRUSTED_HOSTSandCORS_ALLOWED_ORIGINSas appropriate. - Keep
/app/datapersistent: LMDB stores OAuth state and refresh sessions, while SQLite stores users and encrypted provider connections. - Keep exactly one application replica/worker with the packaged embedded databases.
- Never expose provider client secrets to browser or native application bundles.
- Rotate a provider secret in its console and deployment secret manager if exposed.
- Confirm Google/GitHub test users before launch.
9. Verification and troubleshooting¶
After restart, GET /auth/providers should include each fully configured OAuth
provider. API-key providers are always listed. Check these common failures:
| Symptom | Likely cause |
|---|---|
| Provider absent | Both credentials are unset; inspect deployment secret mapping |
| Startup fails mentioning credentials | Only the client ID or secret was supplied |
| Provider reports redirect mismatch | Registered callback differs from APP_BASE_URL |
| OAuth state is invalid | Cookie blocked, callback used another browser, or ten-minute expiry |
| GitHub identity rejected | Account has no email GitHub reports as verified |
| Google identity rejected | ID-token issuer/audience/signature/nonce/email validation failed |
| Link callback returns 401 | Local session/user disappeared before callback |
| Native exchange rejected | Code expired/used, verifier mismatch, or malformed loopback URL |
Run the auth-focused tests with:
uv run pytest tests/test_auth.py tests/test_oauth_extended.py tests/test_email_auth.py -q -ra
Content based on the linked provider documentation was rephrased for compliance with licensing restrictions.