Part 9 of 12 – Conversation Intelligence Platform series Back to Part 1
Authentication is one of those things where the gap between “works” and “done right” is enormous and mostly invisible until something goes wrong. This post is about what “done right” looks like for a platform serving developers, the browser portal, and native apps simultaneously – and what’s changing for existing customers. Quick note on availability: the new platform and Conversation Intelligence API are currently in beta, available to customers enrolled in the carrier services beta programme. API keys are live now. Portal login via OIDC is the next stage.
The platform is designed around three authentication mechanisms. All three are handled by the ForwardAuth shim described in Part 8. All three produce identical claims headers before the request reaches any service. Downstream services have no idea which mechanism was used.
Bearer API keys are for machine integrations – server-to-server calls, automation, CI pipelines. Keys carry scopes at issuance; a key issued with intelligence:read can read Intelligence API resources and nothing else. Keys are stored hashed – the plaintext key is shown once at issuance and never again. Revocation takes effect within one sync cycle, approximately one minute.
This is what’s live now.
Bearer JWTs via Keycloak are for the portal and native apps. The portal uses OAuth 2.0 Authorization Code flow with PKCE – that’s Proof Key for Code Exchange, not passkeys (a different and newer standard). PKCE is an OAuth 2.0 extension that prevents authorization code interception: rather than relying solely on a client secret, it adds a cryptographic challenge-and-verifier pair generated fresh on each login attempt, so intercepting the authorization code alone isn’t enough to get tokens. The access token lives in memory – not localStorage – and the refresh token lives in an httpOnly cookie. This is current best practice for browser-based apps: it prevents XSS token theft while keeping session management clean. Native apps use the same OIDC flow but store tokens in the device secure enclave (Keychain on iOS, Keystore on Android), with credentials entered in the system browser rather than the app itself.
This is the next stage – rolling out shortly after launch.
Basic Auth remains only for the legacy v3 Wholesale API credentials, as a transitional bridge. It is not a platform authentication mechanism and is not available on any new service endpoint. Once the portal Keycloak migration completes and all customers have issued API keys, the v3 Basic Auth bridge will be retired.
The ForwardAuth shim distinguishes these mechanisms by prefix and delegates accordingly. Both paths inject the same claims headers before the request proceeds – account ID, scopes, tier, key identifier. A service should never need to care how a request was authenticated. Its job is to enforce scope and execute business logic, not understand credential formats. The gateway enforces that separation.
Scopes follow the pattern resource:action. Examples:
intelligence:read intelligence:write reporting:read account:admin
Scopes are stored on the key record at issuance and included in the claims on every validated request. Services check scopes before executing any operation. A request attempting a write without the appropriate write scope gets a 403 with a clear error indicating the missing scope.
The scope taxonomy is defined per-service, with each service introducing its own scope root when it’s added to the platform. Account-level scopes are the baseline every account receives; all other service scopes are product-gated. This is how the platform ensures that a Conversation Intelligence API key can’t accidentally – or deliberately – access Billing data.
The Keycloak portal migration will affect existing users when it rolls out. The portal currently authenticates via a custom login flow with credentials stored in localStorage. That will be going away. The replacement is Keycloak running at auth.simwood.com, handling the login UI, credential validation, and token issuance with Simwood branding. For existing portal users, the migration will involve a bulk import of existing credentials into Keycloak and an invitation to set a new password. Once cutover, the old login flow is gone and Keycloak is the only entry point. We’ll communicate directly with all portal users ahead of the switchover.
The security improvement is real. Credentials in localStorage are vulnerable to XSS. Access tokens in memory are not. httpOnly cookies for refresh tokens are not directly accessible to JavaScript at all. This is not a theoretical improvement.
For developers building against the platform: if you’re currently using Basic Auth against v3, nothing changes immediately. You have a minimum 12-month window from the deprecation notice to move to an API key. Your existing integrations continue working during that window. New integrations against new services use API keys from day one. Basic Auth is not available on new service endpoints. That’s a feature, not a limitation.
Previous: Part 8 – A new API architecture