Session lifecycle
Knowing whether you are signed in, signing out cleanly, and reacting to both through typed events.
Three questions, three different answers — and picking the wrong one is how an application either asks for credentials it already has, or navigates away when it meant to check.
Prop
Type
Use isAuthenticated() to render, hasSession() to decide, login() to act.
Observing the session
Five typed events cover the lifecycle. Subscribe once and the whole application can react:
| Event | Fires when | Payload |
|---|---|---|
authenticated | A sign-in completes — or fails | { authenticated: boolean } |
loggedOut | logout() finished its local cleanup, before the network call | {} |
tokenRefreshed | A token is stored, including the sliding refresh | { token } |
unauthorized | A request came back 401, just before it throws | { endpoint, error } |
requestFailed | Any request failed | { endpoint, status, error } |
import { createAtomicClient } from "@sinequa/atomic";
const client = createAtomicClient({
app: "<your-app>",
backendUrl: "https://<your-sinequa-server>",
});
const stops = [
// Fires on success AND failure — read the payload, do not assume.
client.auth.on("authenticated", ({ authenticated }) => {
document.body.dataset.signedIn = String(authenticated);
}),
// Also fires on the sliding refresh, so it is chatty by design.
client.auth.on("tokenRefreshed", () => {
console.debug("session extended");
}),
client.auth.on("loggedOut", () => {
window.location.assign("/");
}),
];
await client.initializeConfig();
await client.auth.login();
// Render from the local check; it is synchronous.
if (client.auth.isAuthenticated()) {
const principal = await client.api.principal.get();
console.log("signed in as", principal.name);
}
// On teardown, so a reload does not stack handlers.
export function dispose() {
for (const stop of stops) stop();
}import { DestroyRef, Injectable, inject, signal } from "@angular/core";
import { SINEQUA } from "./sinequa";
/**
* Mirrors the session into a signal, so templates read it synchronously and views
* update the moment a sign-in, a sign-out or a refresh happens.
*/
@Injectable({ providedIn: "root" })
export class SessionService {
private readonly client = inject(SINEQUA);
readonly signedIn = signal(this.client.auth.isAuthenticated());
constructor() {
const destroyRef = inject(DestroyRef);
// Fires on success and on failure alike — read the payload.
const stopAuth = this.client.auth.on("authenticated", ({ authenticated }) =>
this.signedIn.set(authenticated),
);
const stopOut = this.client.auth.on("loggedOut", () => this.signedIn.set(false));
destroyRef.onDestroy(() => {
stopAuth();
stopOut();
});
}
/** Asks the server only when nothing is stored, and never redirects. */
probe() {
return this.client.auth.hasSession();
}
async signOut() {
const redirectUrl = await this.client.auth.logout();
// The server may hand back a URL to finish signing out at the provider.
if (redirectUrl) window.location.assign(redirectUrl);
}
}import { useEffect, useState } from "react";
import { sinequa } from "./sinequa";
/**
* The session as React state. `isAuthenticated()` is synchronous, so it seeds the
* initial value without a loading phase.
*/
export function useSession() {
const [signedIn, setSignedIn] = useState(() => sinequa.auth.isAuthenticated());
useEffect(() => {
// Fires on success and on failure alike — read the payload.
const stopAuth = sinequa.auth.on("authenticated", ({ authenticated }) =>
setSignedIn(authenticated),
);
const stopOut = sinequa.auth.on("loggedOut", () => setSignedIn(false));
return () => {
stopAuth();
stopOut();
};
}, []);
async function signOut() {
const redirectUrl = await sinequa.auth.logout();
// The server may hand back a URL to finish signing out at the provider.
if (redirectUrl) window.location.assign(redirectUrl);
}
return { signedIn, signOut, probe: () => sinequa.auth.hasSession() };
}The free-function equivalent is onAuthEvent(name, handler), which subscribes on the default client. Both
return the unsubscribe function.
A DOM event is still emitted, for compatibility
An authenticated CustomEvent is dispatched on document — bubbling to window — alongside the typed
channel. Prefer on(): it is typed, and it works with no DOM at all, which the DOM event obviously cannot do in
Node, in a worker or under SSR.
Signing out
logout() does more than clear a token, and the order matters:
The returned URL is how a provider-based deployment finishes signing out at the identity provider. It is
undefined when the server did not supply one.
logout() versus clearSession()
They are not interchangeable, and the difference is deliberate:
logout() | clearSession() | |
|---|---|---|
| Clears the stored token | yes | yes |
| Deletes the session cookie | yes (a request) | no |
| Forgets the credentials marker | yes | no |
| Clears the provider redirect guard | yes | no |
| Marks the client signed out | yes | no |
clearSession() also runs as a pre-flight reset immediately before every login(). That is why it must not
touch the last two: clearing the redirect guard there would defeat the loop protection, and forgetting the
marker would make an ambiguous mode unresolvable.