Kinotic
Security

Authentication

Authentication methods and identity management in Kinotic.

Overview

Kinotic supports multiple authentication methods to secure your applications. Every connection to a Kinotic server requires authentication — the platform identifies the caller and establishes a session before any service calls are allowed.

Authentication Methods

  • Email and Password — Built-in user management with secure credential storage. Ideal for getting started quickly or for applications that manage their own user base.
  • OIDC Providers — Connect any standard OpenID Connect provider including Google, Microsoft, Okta, and others. Your organization admin enables OIDC configurations for your application — including platform-provided social providers pre-registered with Kinotic OS.

This page covers what an application developer needs to know. For the platform-level model behind these mechanisms — scope isolation, the OidcConfiguration entity, signup and login flows — see System Security and Organization Management.

Scoped Authentication

Authentication happens at the WebSocket upgrade (handshake), not in a STOMP CONNECT frame. Every authentication carries a scope that identifies which layer the caller is authenticating against. Scope is structural — it is determined by which of two optional ids are supplied:

organizationIdapplicationIdResulting scope
absentabsentSYSTEM
presentabsentORGANIZATION
presentpresentAPPLICATION

For application developers this is always the APPLICATION scope, so both ids are supplied: your organization's id plus your application's id. Scope isolation ensures users are authenticated against the correct user pool — a user in Application A cannot access Application B, even if both applications use the same OIDC provider.

Connecting with Email and Password

In Node, pass a BasicCredentialsResolver carrying the credential and scope values (clientId, clientSecret, organizationId, applicationId); they are presented as headers on the WebSocket upgrade:

import { BasicCredentialsResolver, Kinotic } from '@kinotic-ai/core'
import { ensureNodeWebSocket } from '@kinotic-ai/core/node'

ensureNodeWebSocket()
await Kinotic.connect({
    credentials: new BasicCredentialsResolver('user@example.com', 'password', 'my-organization', 'my-application')
})

The constructor is BasicCredentialsResolver(clientId, clientSecret, organizationId?, applicationId?). The clientId is your user's email and clientSecret is the password — or a machine identity's id and provisioned secret. The platform looks up the user by email within your application's scope, verifies the password against a securely stored bcrypt hash, and establishes a session.

Your application's users and machines connect to the app server, at your application's API host (the VITE_KINOTIC_HOST a UI build receives, <organizationId>--<applicationId> under the platform's application domain) or at the app server's own address. The app server authenticates only application identities and the runtime workloads of your organization's applications: organization and platform credentials fail there as invalid, and on an application's API host so do the credentials of any other application.

With no credentials at all, Kinotic.connect() resolves through the default chain: the KINOTIC_CLIENT_ID / KINOTIC_CLIENT_SECRET (plus optional KINOTIC_ORGANIZATION_ID / KINOTIC_APPLICATION_ID) environment variables, then the browser session. Server settings resolve the same way — the server option, the KINOTIC_SERVER_HOST/PORT/USE_SSL variables, the page's own location in a browser, then the management server at https://api.kinotic.ai, so an application's client names its API host. To pin the server explicitly, pass it alongside the credentials:

await Kinotic.connect({
    server: { host: 'kinotic.example.com', useSSL: true },
    credentials: new BasicCredentialsResolver('user@example.com', 'password', 'my-organization', 'my-application')
})

Absent server fields fall through to the environment — { host } alone means no TLS and port 58503, the management server's gateway port, so a connection to a local app server names its port, 58505.

Connecting with OIDC

For token-based authentication, pass a Bearer token via BearerCredentialsResolver. The resolver sends Authorization: Bearer <token> on every (re)connect; pass a supplier function to refresh a short-lived token before each connect:

import { BearerCredentialsResolver, Kinotic } from '@kinotic-ai/core'
import { ensureNodeWebSocket } from '@kinotic-ai/core/node'

ensureNodeWebSocket()
await Kinotic.connect({
    credentials: new BearerCredentialsResolver(async () => await getToken())
})

A Kinotic-minted JWT already carries the organizationId / applicationId claims, so no scope headers are needed alongside it. The platform validates the JWT against the OIDC configurations enabled for your application, matches the token's email to a pre-existing user in your application's scope, and establishes a session.

Browser applications

A browser UI does not supply credentials. It signs its user in over REST at its application's API host, the VITE_KINOTIC_HOST of the UI build contract, which sets a session cookie for that host, then connects to the same host with no credentials — the default resolution finds the session. The host names the application, so the UI never sends its organization or application id:

import { Kinotic } from '@kinotic-ai/core'

const env = import.meta.env
const server = { host: env.VITE_KINOTIC_HOST, port: parseInt(env.VITE_KINOTIC_PORT), useSSL: env.VITE_KINOTIC_USE_SSL === 'true' }
const api = `${server.useSSL ? 'https' : 'http'}://${server.host}:${server.port}/api/auth`
const post = (path: string, body: object) => fetch(`${api}${path}`, {
    method: 'POST',
    credentials: 'include',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(body),
})

// the password form
if ((await post('/app/login', { email, password })).status === 204) {
    await Kinotic.connect({ server })
}

// one "Continue with <name>" button per provider the login offers
const providers = await (await fetch(`${api}/app/login/providers`, { credentials: 'include' })).json()
async function continueWith(provider: { id: string }) {
    const start = await (await post(`/app/login/oidc/start/${provider.id}`, {})).json()
    window.location.assign(start.redirect)
}

The login page offers every method the application supports and lets the user choose: the password form, and a button for each configuration GET /api/auth/app/login/providers lists as [{id, name, provider}], whether the organization's own provider or one of the platform's shared Google and Microsoft registrations. No endpoint takes an email before the user has chosen a method, so a login page cannot be used to learn which emails have an account. An AI assistant building the page can ask the same question through the ApplicationService.getLoginProviders MCP tool.

A login belongs to the page that made it, and the API host accepts one only from a page that is one of the application's own UIs. GET /api/auth/me answers 204 while the page is signed in, and POST /api/auth/logout signs it out; both take credentials: 'include' as well.

Signing in to a tenant

An application whose tenants its administrators manage gives each tenant a login of its own. A page served at one of the tenant's sites, <tenantId>--<applicationId>--<uiName> under your organization's subdomain of the sites domain (see Multi-Tenancy), signs in to that tenant with the same calls as above: the API host reads the tenant from the page's host, so /app/login/providers lists what the tenant's login offers — the tenant's own providers, then the application's — and /app/login admits only the tenant's users. A page that is not one of the tenant's sites, such as a UI running locally, names the tenant instead, with ?tenantId= on the GET and the start, and tenantId in the login body; the API host answers 404 for a tenant the application does not have, and 403 when a tenant's site names another tenant.

How OIDC Works with Your Application

  1. Your organization administrator creates one or more ApplicationOidcConfigurations for your application's login, or TenantOidcConfigurations for one tenant's, in the portal under the application's Settings → Sign-in providers tab, and registers the callback URL the tab shows for each, https://<organizationId>--<applicationId>.<app API domain>/api/auth/app/login/oidc/callback/<configId>, with the provider; or names one of the platform's shared Google and Microsoft registrations in the application's socialProviders
  2. Your UI renders a button per provider the login offers; the button posts the configuration's id to the start at your API host, whose answer carries the URL to send the browser to
  3. The provider returns the browser to the API host, which validates the provider's response, looks the user up in your application, or in the tenant the sign-in is to, and sets the session cookie. A shared registration returns the browser to the app server's shared host instead, which validates the response and hands the verified identity to your application's API host under a single-use code, which completes only in the browser that started the sign-in; that host looks the user up and sets the cookie
  4. The browser returns to / on the site that started the sign-in, which connects with the session; a sign-in that fails returns instead to the /login page of your application's primary UI with an error parameter

Reusable Providers

Kinotic OS registers once with Google and Microsoft. The platform operator provisions these as ApplicationSocialOidcConfiguration entities, and your application offers one by naming its provider kind, google or azure-ad, in its socialProviders — no registration of your own with the provider. The registration is shared by every application that offers it, so a user who revokes it at the provider revokes it for all of them. Your organization's own providers for your application are ApplicationOidcConfiguration entities your organization admin creates for it on the application's Sign-in providers settings tab, each registered with the provider under the application's own callback URL; the organization's own enterprise SSO for the portal is an OrganizationOidcConfiguration, and an admin who wants the same provider at both copies it from one scope to the other. An ApplicationOidcConfiguration whose createUserOnSignIn is on creates a user of your application from an identity it verifies on that identity's first sign-in, when the platform manages your application's tenants; see below.

All are kinds of OidcConfiguration — see System Security for how ownership is captured by the subtype.

User Management

Application-scope users are pre-created by an administrator before they can authenticate, unless one of your application's own ApplicationOidcConfigurations says otherwise. Having a valid Google account never grants access to your application by itself: the platform's shared registrations create no users. A configuration your organization registered for the application may have createUserOnSignIn on, and then an identity that provider verifies which no user is bound to (an OidcBinding of the provider's configuration and sub to a user) becomes a user of the application on its first sign-in, with a tenant of its own, which is why it applies only while the platform manages the application's tenants. Under tenant administrators a user is never without a tenant, so there a user is created by an administrator, an invitation, or a tenant's own TenantOidcConfiguration with createUserOnSignIn, which makes the identity its provider verifies a user of that tenant on its first sign-in at the tenant's login; the application's own configurations never create a user into a tenant. An identity whose verified email the application already holds under another method is refused with account_exists. A configuration of your own for Google with createUserOnSignIn admits any Google account, which is sign-up with Google for your application. Self-service signup with a form applies to the Organization scope only.

Your organization administrator manages your application's users from the organization's Members page:

  • Local users are created with an email and password
  • OIDC users are created with an email only — the OIDC subject is linked automatically on first login

For details on user and OIDC configuration management, see the Organization Management platform guide.

Copyright © 2026