@sinequa/atomic

OAuth and SAML

Redirecting to an identity provider, coming back correctly, and the loop guard that stops a misconfigured callback from cycling forever.

Both modes work the same way: the server hands you a URL, the browser leaves for the identity provider, and it comes back with a session cookie already set. Your interface renders a loader and nothing else — there is no form, and no token for you to handle.

the call that navigates away
import { AuthMode } from "@sinequa/atomic";

client.configure({ authMode: AuthMode.oauth("my-provider") });

// This may never return: the page is left.
await client.auth.login();

The round trip

POST security.oauth (provider, originalUrl) redirectUrl full-page navigation callback back on originalUrl, session cookie set a one-shot guard is set, then the browser navigates the application boots again — and must probe, not redirect Your application Sinequa server Identity provider

The critical part is the second boot. The application restarts with a session it did not establish in this page load, so it has to check rather than sign in again.

Coming back

Sign in, and handle the return correctly
src/main.ts
import { AuthMode, createAtomicClient } from "@sinequa/atomic";

const client = createAtomicClient({
  app: "<your-app>",
  backendUrl: "https://<your-sinequa-server>",
  authMode: AuthMode.oauth("my-provider"),
});

await client.initializeConfig();

// First: is there already a session? This is the return-from-provider case, and it
// never redirects — unlike login(), which would navigate away again.
if (await client.auth.hasSession()) {
  start();
} else if (client.auth.isRedirectPending()) {
  // We came back from the provider without a usable session. Redirecting again
  // would loop, so say what happened instead.
  showError("Sign-in completed at the provider, but no session reached this application.");
} else {
  // A genuine first visit: this navigates away and does not return.
  await client.auth.login();
}

function start() {
  console.log("signed in");
}

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

The loop guard

Before asking the server for a sign-in URL, the library checks a one-shot flag in sessionStorage. If it is already set — meaning we redirected once and came back without a session — it throws instead of redirecting again:

the error you will see
OAuth sign-in with provider "my-provider" did not establish a session — its callback
is likely not authorized for this application.

Two details of its design are deliberate, and both fixed real loops:

  • It is checked before the server call, not after. A provider we already know sends us back empty-handed should not be asked for another URL.
  • It is not consumed when it fires. An earlier version removed the flag while raising the error, so the next 401 — an application writing user settings while unauthenticated, say — triggered a recovery that signed in again, found no guard, and redirected once more. The application cycled through the provider about once a second.

It is cleared by logout(), by any session actually being established, and by a successful hasSession(). A failed probe leaves it alone, so the protection stays intact.

Configuring the provider

Two ways, and the mode wins:

provider resolution order
client.configure({ authMode: AuthMode.oauth("identity") }); // preferred
client.configure({ autoOAuthProvider: "identity" });        // legacy key, still read

The server's pre-login response may also announce one, which is what makes auto-detection work without any client configuration at all.

With neither, login() throws and names both ways to fix it. A client created with createAtomicClient and no autoOAuthProvider must carry the provider in the mode.

Pitfalls

What's next

On this page