@sinequa/atomic

Auto-detection

What happens when you leave the mode unset — the precedence rules, the ambient probe, and how an ambiguous session is resolved.

unknown is the default, and it is not a placeholder: it is a request to work the mode out. Two mechanisms do that, at two different moments, and confusing them makes the behaviour look erratic.

MechanismWhenHow
DetectioninitializeConfig()Pure, no I/O: config plus the server's pre-login response
Ambient probelogin()A real request, to see whether the transport authenticates

Detection: the precedence

detectAuthMode(config, preLogin) decides, in this order:

yes no yes no yes no yes no authMode set andnot unknown? that mode wins —always a bearer token? bearer an OAuth provider?config, then pre-login oauth a SAML provider?config, then pre-login saml unknown —resolved at login()

Two things follow. Declaring a mode is absolute: an explicit sso, bearer, oauth or saml is never overridden by what the server announces. And OAuth wins over SAML when both are configured.

This is a fixed regression, worth knowing if you are on 2.2 or earlier

detectAuthMode used to recognise credentials only. Any other explicit mode was discarded and replaced by unknown unless the matching legacy key happened to be set too — so an application that had declared exactly how it authenticates got a username and password prompt.

The probe: resolving unknown at sign-in

With the mode still unknown, login() runs the ambient handshake — a request to a protected endpoint that lets the server challenge it:

  • It succeeds → the mode becomes sso, and the session is recorded.
  • It fails → the mode becomes credentials, and a form is what your interface should render.

That fallback is announced in the logs rather than done silently, because the reason is almost always a configuration the client never received: no token for a token-authenticated server, or a provider that neither the configuration nor the pre-login response declared.

Let it resolve, then render accordingly
src/main.ts
import { createAtomicClient, isExternalAuthMode } from "@sinequa/atomic";

// No authMode: the default is unknown, which means "work it out".
const client = createAtomicClient({
  app: "<your-app>",
  backendUrl: "https://<your-sinequa-server>",
});

// Reads the pre-login response and applies the precedence above.
await client.initializeConfig();
console.log("detected:", client.config.authMode?.kind);

const signedIn = await client.auth.login();

if (signedIn) {
  start();
} else if (isExternalAuthMode(client.config.authMode)) {
  // oauth and saml navigate away, so reaching here means the redirect did not
  // happen or came back empty — not a case for a password form.
  showError("Sign-in did not complete.");
} else {
  // Resolved to credentials: the form owns the sign-in from here.
  showForm();
}

function start() {}
function showForm() {}
function showError(message: string) {
  console.error(message);
}

When a session already exists under unknown

A subtler case: login() finds a valid session, but nothing on the wire says whether it came from a form or from the host. It is resolved like this, in order:

  1. A configured transport — an SPFx AadHttpClient, any injected httpAdapter — settles it as sso. Such a transport authenticates every request itself, so the session cannot have come from a form. This is a fact about the current page.
  2. Otherwise, a marker in localStorage written by the last credentials sign-in settles it as credentials.
  3. Otherwise, sso.

A real session found by the ambient probe also takes precedence over the marker: if the handshake succeeds, the mode is sso even when the marker says otherwise.

The marker is shared per origin

localStorage is per origin, not per application. Serving an SPFx build from the same host as a credentials-based application — an ng serve on localhost, typically — used to make it label the host-authenticated session credentials, and then offer a login form for an application its host had already authenticated.

Rule 1 exists for that reason. An adapter that does not authenticate (one added for logging or retries) should declare authMode explicitly rather than rely on this resolution.

Pitfalls

What's next

On this page