Authentication
Local sessions, hosted identity, and authorization boundaries.
The hosted plane uses the existing Auth0 tenant and a confidential web
application's authorization-code flow. /login and /signup redirect to
Auth0; /auth/callback exchanges the code on the server and creates the
Brainlyy session only after validating the identity response.
Identity and registration compatibility
The deployment still supplies AUTH0_DOMAIN, AUTH0_CLIENT_ID,
AUTH0_CLIENT_SECRET, PLANE_BASE_URL, and PLANE_SECRET. No tenant,
application, account, connection, credential, or registered callback was
created, changed, or inspected during this implementation.
The callback remains ${PLANE_BASE_URL}/auth/callback. Google still uses the
google-oauth2 connection; GitHub still uses github. Sign-up adds Auth0's
screen_hint=signup and shares the same checks as sign-in. Logout keeps the
existing Auth0 /v2/logout redirect and return origin.
Accounts remain keyed by Auth0's exact sub, including existing auth0|,
google-oauth2|, and github| subjects. A matching email never merges
subjects. Existing account rows, machine registrations, session epochs, and
application session cookies retain their formats.
Validation before a session
- A new login generates independent random state and nonce values. They
travel in an HMAC-signed, ten-minute
plane_oauthcookie. State is compared before any token exchange. Cookies remainHttpOnly,SameSite=Lax, andSecurewhenPLANE_BASE_URLuses HTTPS. - The code is exchanged at the configured tenant's
/oauth/tokenendpoint. The client secret stays in that server request. The response must contain a nonempty string access token with the Bearer token type. PyJWT[crypto]>=2.13,<3verifies the ID token's RS256 signature using the configured tenant's/.well-known/jwks.json. Token headers cannot choose a signing algorithm or another key source. Unsigned and HMAC tokens fail.- The verified token must contain
iss,sub,aud,exp,iat, andnonce. Issuer matching includes Auth0's trailing slash; audience matching uses the application's client ID. Expiry, future issued-at/not-before, numeric claim types, a nonempty subject, and the expected nonce are checked. Multiple audiences require a matchingazp; any suppliedazpmust match the client ID. No clock-skew allowance is configured. /userinfomust identify exactly the verified token's subject. Only then can the allowlist, account upsert, and application session creation run.
This follows Auth0's ID token validation guidance and the OpenID Connect requirements for ID tokens and userinfo subject matching.
The JWKS client caches key sets for five minutes and refreshes once when a
token names an unknown kid. Individual keys are not cached indefinitely,
so removed keys cease to work after the set expires. Multiple signing keys
can coexist during Auth0 rotation. See Auth0's JWKS guidance
and PyJWT's key caching behavior.
Endpoint URLs derive solely from AUTH0_DOMAIN, a hostname with an optional
port. Remote endpoint URLs use HTTPS. Plain HTTP is reserved for explicit
loopback fixtures (127.0.0.1, localhost, or ::1); tests do not disable
TLS certificate checking. Each upstream fetch has a 15-second caller deadline.
Token exchange, userinfo and JWKS use direct configured endpoints; redirects and environment proxies are refused. HTTPS uses Python's default verified TLS context. A fetch deadline covers DNS, TLS, headers, body and JSON parsing. Two transport workers are shared, with immediate admission refusal and no queue. Encoded form requests are capped at 128 KiB, bearer values at 16 KiB, initial and interim status/headers at 32 KiB, bodies at 512 KiB, and consumed plaintext HTTP bytes (including chunk framing and trailers) at 640 KiB. TLS/socket buffering is outside that application-parser count. Responses must be UTF-8 JSON objects with no more than 64 nesting levels.
A timed-out DNS/platform operation can outlive its caller and retains its worker slot until it exits. The worker checks the deadline again before sending credentials after connection setup. A full sign-in can perform sequential fetches and an unknown-key refresh; the deadline applies to each fetch.
Failure and session behavior
Invalid identity tokens return a safe error page without creating an account or session. Unavailable token/profile/key endpoints fail closed. Upstream response bodies, tokens, client secrets, and library exception details are not rendered. The transaction cookie is cleared on successful, refused, cancelled, or failed callbacks. A failed login leaves an already signed-in application session intact.
The browser's cleared cookie prevents normal callback repetition. Auth0's single-use authorization code remains the server-side replay boundary; the plane does not maintain a persistent consumed-state ledger. This change does not introduce refresh-token storage or alter application session revocation.
Account identity across deletion and migration
Browser cookies identify an account by its signed numeric user ID and session epoch. The user-ID allocator now keeps a durable high-water mark rather than reusing SQLite's deleted highest row ID. Migration seeds that counter from surviving accounts and account references, including orphaned gateway-token rows, before removing gateway credentials whose account no longer exists. Account deletion also removes that account's gateway credentials in the same transaction. The counter retains one integer, not deleted profile details.
Existing accounts keep their IDs, Auth0 subjects, session epochs and valid credentials. New accounts receive an unpredictable initial epoch between 1 and 2^62; refreshing an existing profile leaves its epoch unchanged. This additional binding prevents old epoch-zero cookies from naming new accounts when an older database has already lost all records of a previously deleted numeric ID. "Sign out everywhere" still increments the current account's epoch.
The migration cannot distinguish two cookies that already named the same reassigned ID and epoch before the upgrade: legacy cookies did not contain a subject binding. A known affected account requires a targeted session-epoch revocation. A legacy gateway credential whose user ID was already reassigned also lacks the old subject binding and needs separate targeted credential revocation; changing the browser epoch does not revoke gateway tokens. There is no blanket logout here. These changes and the deletion, migration, cookie and gateway-token regressions were validated against disposable local databases; no live tenant, account, session or deployment was changed.
Local evidence and remaining validation
Run:
python tests/test_authentication.py
python tests/test_native_authentication.py
python tests/test_oidc_transport.py
python tests/test_plane_user_identity.py
python tests/test_plane.py
python tests/test_journey.py
The authentication suite was written before the implementation. Its initial 25 tests exposed 42 failing subcases against the previous callback, including acceptance of forged signatures, wrong issuers/audiences, and expired tokens. A second failing test exposed five malformed access-token/token-type cases. The completed suite has 30 passing tests. The existing plane suite passed 236 assertions and the local end-to-end journey passed 24 assertions.
The separate OIDC transport suite has 19 passing tests after failing regressions for proxy/redirect refusal, request/header/body/chunk bounds, strict UTF-8/object JSON, nesting, slow responses, and caller deadlines including stalled DNS. It also checks retained worker admission, trusted/untrusted TLS and hostname validation, valid maximum-size/depth responses, and JWKS cache preservation after a failed refresh. Hosted 30-test and native 18-test authentication regressions passed again after the transport change.
The fixture generates real RSA keys, signs ID tokens, and serves public JWKS over loopback HTTP. It covers key rotation/removal, required claims, nonce, state expiry/tampering/replacement, callback failures, separate federated subjects, and reuse of an existing account. These results prove local code behavior. They do not prove a live Auth0, Google, or GitHub authentication journey, a production TLS/proxy configuration, or a deployed session.
Before release, validate real returning and new provider accounts against the existing Auth0 registration, including its RS256 setting, allowed callbacks, logout URLs, cancellation, and key rotation behavior. No deployment or live tenant verification was performed here.
Native system-browser sign-in
The native Account menu implements authorization code with PKCE S256 using
openidconnect 4 and the operating system's normal browser. No confidential
web client secret is embedded or sent by the desktop. The webview receives no
native commands, provider tokens, or native account session credential.
The desktop release uses https://app.brainlyy.xyz/ as its public account
service, so ordinary sign-in needs no config editing or CLI. A distributor can
select a different origin at build time with BRAINLYY_DESKTOP_PLANE_URL.
An explicit self-hosted plane.url takes precedence; an invalid override is
refused rather than silently selecting another service. Generic Python package
defaults remain unchanged. A first launch makes no account request until the
person chooses a menu action. An existing saved session is rechecked on startup
only when it belongs to the selected origin. Changing origins never forwards
the saved credential to the new service.
An unavailable or unconfigured account service gives a clear native menu status. Local setup and the workspace remain usable. Check account status retries availability. Cancel sign-in or check cancels the pending action; the worker drains its bounded request before another attempt is admitted. Closing the native app also cancels the worker and prevents a later session file write. Failed or cancelled sign-in preserves a previous desktop session.
System-browser handoff has its own one-worker bound. Cancellation or timeout returns even if the OS launcher stalls; its slot stays occupied until it exits, and a retry cannot accumulate more launchers. The OS may complete an already started handoff later, but the cancelled callback is closed and cannot save a session. No unrelated browser process is terminated.
Operator registration prerequisites
The plane must supply these optional settings in addition to its existing web
settings. They configure a separate public Native application in the same
Auth0 tenant, with authorization code enabled and token endpoint
authentication set to none:
| Setting | Meaning |
|---|---|
AUTH0_NATIVE_CLIENT_ID |
Native public client ID, distinct from AUTH0_CLIENT_ID |
AUTH0_NATIVE_AUDIENCE |
Auth0 API identifier for the native access token |
AUTH0_NATIVE_CALLBACK_URL |
Exactly http://127.0.0.1:<registered-port>/auth/callback |
The exact callback must be registered for that native application. The app binds that fixed numeric loopback address before opening the system browser; it refuses a busy port and never falls back to an unregistered random port. The callback listener is separate from the desktop UI service. This design does not assume Auth0 supports wildcard callback ports. Existing hosted client credentials, callback URLs, Auth0 subjects, and provider connections remain unchanged. The native application must be authorized for the intended existing connections by the operator; code alone does not prove that tenant setup.
See Auth0's public-client PKCE walkthrough and OAuth for native apps.
Native validation and session boundaries
Each attempt creates independent state, nonce, and an S256 code challenge. The callback requires the exact path and Host, one matching state, and one unambiguous code or error. Duplicate parameters, a mismatched returned issuer, and malformed callbacks are refused. Invalid requests do not consume the pending attempt. A valid callback is consumed once; the listener closes on success, cancellation, or the five-minute attempt deadline.
The maintained OIDC library discovers keys afresh for each sign-in and checks
the ID token's RS256 signature, exact issuer, native-client audience, expiry,
issued-at time, and nonce. Any supplied azp must identify the native client;
additional untrusted audiences fail. The access-token hash is verified when
present. Userinfo must have the same subject. Discovery, key, token, and
userinfo requests are restricted to the configured issuer's exact Auth0
endpoints. Native HTTP uses verified platform TLS, no environment proxies or
redirects, 128 KiB response bounds, 15-second request limits, and the remaining
attempt deadline. Plain HTTP for the issuer exists only in compiled unit-test
fixtures; an explicit numeric loopback HTTP plane supports local development.
POST /native/session separately verifies the API access token, including
signature, issuer, API audience, native azp/client_id, required openid
scope, expiry, and issued-at/not-before values. It matches userinfo's subject
and applies the existing allowlist before reusing the exact subject-keyed
account. Two native sign-ins per plane process can be checked concurrently.
GET /native/config contains only public registration information.
The returned opaque bn_ credential authorizes only GET /native/account
and DELETE /native/session. It cannot authorize the hosted browser session,
MCP gateway, relay, or remote Work. It grants no machine pairing or resource
access. SQLite stores only its hash, user/session epoch, client, and expiry.
Sessions last 30 days, are capped at ten per account, and are revoked by the
existing global epoch change or account deletion. The native account endpoint
refuses expired/revoked sessions; the desktop requests a new sign-in.
The desktop saves only this app credential and bounded account metadata under
its private instance/.account/session.json, bound to one plane origin.
Writes replace the file atomically. POSIX uses a 0700 directory and 0600 files;
Windows uses the existing per-user app-data ACL pattern. The hidden directory
is excluded from source access. This is owner-private file persistence;
platform credential-store encryption is not implemented. Other processes
running as the same OS user, or an administrator, remain outside this file
boundary. Loopback binding and PKCE do not establish app identity against every
same-user process; see Auth0's app-impersonation guidance.
Sign out of this desktop removes the local credential and revokes that native app session when the plane is reachable. If revocation is unavailable, the menu says it signed out locally; the server session retains its expiry. The system browser's Auth0/provider SSO may remain. No refresh token is requested or saved, and this action does not log out other browsers or devices.
Native fixture evidence and remaining qualification
python tests/test_native_authentication.py
python -m unittest discover -s desktop/tests -v
cargo test --locked --manifest-path desktop/src-tauri/Cargo.toml
cargo fmt --manifest-path desktop/src-tauri/Cargo.toml -- --check
The plane suite has 18 tests using real RSA-signed access tokens and a local JWKS/userinfo fixture. The first 17 tests were run before implementation and failed against missing endpoints. Native protocol, signed-token/PKCE flow, menu persistence/cancellation, deadline-edge, and release-origin tests also have captured failing runs before their implementations. The final Rust suite has 33 passing tests and one ignored child-process fixture invoked by lifecycle tests; the sidecar suite has eight passing tests. Native coverage includes a complete loopback PKCE exchange with an injected browser opener, key rotation on a fresh sign-in at the same issuer, userinfo/session subject mismatch, callback collision/replay, HTTP bounds, restart checks, logout, and cancellation without late persistence. Tests launch no visible browser or native window. Blocked OS-launcher fixtures cover cancellation, timeout and retry admission.
These fixtures do not qualify the live Auth0 native application, deployed plane endpoints, system-browser handoff, or actual menu/window interaction on any OS. Real returning/new accounts and enabled provider connections, callback registration, cancellation/retry, key rotation, and logout still need live validation on each release target. Existing native window/installer evidence predates this increment; rebuild and repeat those checks. No live tenant changes, credential inspection, deployment, or signing occurred here. The existing confidential hosted web flow still does not send a PKCE challenge.