Windows and reverse-proxy SSO
When the transport authenticates every request by itself — and why this mode is the only one that lets the server run its challenge.
In this mode nobody signs in: the request arrives already authenticated. Windows integrated authentication
through IIS, a reverse proxy injecting a header, an SPFx host attaching an Azure AD token — from the library's
point of view they are the same thing, and the mode is called sso.
Your interface renders nothing. At most a loader while the handshake happens.
import { AuthMode } from "@sinequa/atomic";
client.configure({ authMode: AuthMode.sso() });
await client.auth.login();What makes this mode different
Every other mode tells the server not to challenge the request. sso is the only one that lets it:
That flag is what a Windows or proxy deployment needs. Sent as true, the server answers anonymously and the
session is never established — which surfaces as a login form in an application that has no password to ask
for.
The handshake is a request to a protected endpoint
Fetching a CSRF token cannot establish an ambient session: that call passes suppressErrors=true, which asks
the server explicitly not to challenge. Only a plain request to a protected endpoint — the configured
probeEndpoint, api/v1/principal by default — makes IIS or the proxy authenticate. See
How it works.
Signing in, and staying signed in
import { AuthMode, createAtomicClient } from "@sinequa/atomic";
const client = createAtomicClient({
app: "<your-app>",
backendUrl: "https://<your-sinequa-server>",
authMode: AuthMode.sso(),
});
await client.initializeConfig();
if (await client.auth.login()) {
// There may be no token at all — see below. Do not test getToken().
const principal = await client.api.principal.get();
console.log("signed in as", principal.name);
} else {
// The handshake failed. The mode is left as sso: nothing proved it wrong, and
// offering a password form for an authentication that has none is a dead end.
console.error("The server did not authenticate this request.");
}import { InjectionToken, inject, provideAppInitializer } from "@angular/core";
import { AuthMode, type AtomicClient, createAtomicClient } from "@sinequa/atomic";
export const SINEQUA = new InjectionToken<AtomicClient>("Sinequa client", {
factory: () =>
createAtomicClient({
app: "<your-app>",
backendUrl: "https://<your-sinequa-server>",
authMode: AuthMode.sso(),
}),
});
/**
* The handshake happens before the first view. A failure is recorded rather than
* thrown, so the application can render an explanation instead of a blank page.
*/
export const ssoFailed = { value: false };
export function provideSinequa() {
return provideAppInitializer(async () => {
const client = inject(SINEQUA);
await client.initializeConfig();
ssoFailed.value = !(await client.auth.login());
});
}import { ChangeDetectionStrategy, Component, inject, resource } from "@angular/core";
import { SINEQUA, ssoFailed } from "./sinequa";
@Component({
selector: "app-root",
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
@if (failed) {
<p role="alert">
The server did not authenticate this request. Check that the site is in a trusted zone.
</p>
} @else if (principal.value(); as user) {
<p>Signed in as {{ user.name }}</p>
}
`,
})
export class AppComponent {
private readonly client = inject(SINEQUA);
readonly failed = ssoFailed.value;
readonly principal = resource({ loader: () => this.client.api.principal.get() });
}import { AuthMode, createAtomicClient } from "@sinequa/atomic";
export const sinequa = createAtomicClient({
app: "<your-app>",
backendUrl: "https://<your-sinequa-server>",
authMode: AuthMode.sso(),
});
/** Resolves to whether the ambient handshake succeeded. */
export const ready = (async () => {
await sinequa.initializeConfig();
return sinequa.auth.login();
})();import type { Principal } from "@sinequa/atomic";
import { useEffect, useState } from "react";
import { ready, sinequa } from "./sinequa";
export function App() {
const [state, setState] = useState<"pending" | "failed" | Principal>("pending");
useEffect(() => {
let active = true;
ready
.then(async (signedIn) => {
if (!active) return;
// A failure here is not a wrong password: the transport did not authenticate.
setState(signedIn ? await sinequa.api.principal.get() : "failed");
})
.catch(() => active && setState("failed"));
return () => {
active = false;
};
}, []);
if (state === "pending") return <p>Signing you in…</p>;
if (state === "failed")
return (
<p role="alert">
The server did not authenticate this request. Check that the site is in a trusted zone.
</p>
);
return <p>Signed in as {state.name}</p>;
}A session with no token
This is the part that trips up code written against the other modes: an ambient SSO session may hold no CSRF token at all. The server authenticates each request on its own, so there is nothing to store.
isAuthenticated() handles it — it falls back to a stored flag rather than requiring a token. But your own code
must not:
if (getToken()) start(); // wrong: token-less SSO has none
if (client.auth.isAuthenticated()) start(); // right