Username and password
A sign-in form wired to login(), including what to do with a refused password and why the form owns the flow.
The mode where your interface does the work: nothing can be signed in automatically, so a form collects the
credentials and hands them to login().
await client.auth.login({ username, password });Declaring the mode skips a request
initializeConfig() makes no pre-login request when authMode is already credentials — a form needs
nothing from the server. It also clears any provider or token configuration, since none of it applies.
A complete sign-in form
Sign in, with the three failure cases handled
import { AuthMode, UnauthorizedError, createAtomicClient } from "@sinequa/atomic";
const client = createAtomicClient({
app: "<your-app>",
backendUrl: "https://<your-sinequa-server>",
authMode: AuthMode.credentials(),
});
await client.initializeConfig();
// A session may already exist — from another tab, or a page reload. login() with no
// arguments reports that truthfully, so do not render the form before asking.
if (await client.auth.login()) {
start();
} else {
renderForm();
}
function renderForm() {
const form = document.querySelector("form")!;
const error = document.querySelector("#error")!;
form.addEventListener("submit", async (event) => {
event.preventDefault();
error.textContent = "";
const data = new FormData(form);
const username = String(data.get("username") ?? "");
const password = String(data.get("password") ?? "");
try {
await client.auth.login({ username, password });
start();
} catch (cause) {
// A refused password throws — it does not return false.
error.textContent =
cause instanceof UnauthorizedError
? "Wrong username or password."
: "Could not reach the server.";
}
});
}
function start() {
console.log("signed in");
}import { ChangeDetectionStrategy, Component, inject, signal } from "@angular/core";
import { UnauthorizedError } from "@sinequa/atomic";
import { SINEQUA } from "./sinequa";
@Component({
selector: "app-sign-in",
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<form (submit)="signIn($event)">
<input name="username" [value]="username()" (input)="username.set(value($event))" autocomplete="username" />
<input
name="password"
type="password"
[value]="password()"
(input)="password.set(value($event))"
autocomplete="current-password"
/>
<button type="submit" [disabled]="busy() || !username() || !password()">
{{ busy() ? 'Signing in…' : 'Sign in' }}
</button>
@if (error()) {
<p role="alert">{{ error() }}</p>
}
</form>
`,
})
export class SignInComponent {
private readonly client = inject(SINEQUA);
readonly username = signal("");
readonly password = signal("");
readonly busy = signal(false);
readonly error = signal("");
async signIn(event: Event) {
event.preventDefault();
this.busy.set(true);
this.error.set("");
try {
await this.client.auth.login({ username: this.username(), password: this.password() });
// Navigate, or let a guard react to the `authenticated` event.
} catch (cause) {
// A refused password throws; it does not return false.
this.error.set(
cause instanceof UnauthorizedError
? "Wrong username or password."
: "Could not reach the server.",
);
} finally {
this.busy.set(false);
}
}
value(event: Event) {
return (event.target as HTMLInputElement).value;
}
}import type { ApplicationConfig } from "@angular/core";
import { inject, provideAppInitializer } from "@angular/core";
import { SINEQUA } from "./sinequa";
/**
* In credentials mode the initializer must not fail when there is no session:
* login() returning false is the normal path to the form.
*/
export const appConfig: ApplicationConfig = {
providers: [
provideAppInitializer(async () => {
const client = inject(SINEQUA);
await client.initializeConfig();
await client.auth.login(); // true if a session already exists
}),
],
};import { UnauthorizedError } from "@sinequa/atomic";
import { type FormEvent, useState } from "react";
import { sinequa } from "./sinequa";
export function SignIn({ onSignedIn }: { onSignedIn: () => void }) {
const [username, setUsername] = useState("");
const [password, setPassword] = useState("");
const [busy, setBusy] = useState(false);
const [error, setError] = useState("");
async function submit(event: FormEvent) {
event.preventDefault();
setBusy(true);
setError("");
try {
await sinequa.auth.login({ username, password });
onSignedIn();
} catch (cause) {
// A refused password throws; it does not return false.
setError(
cause instanceof UnauthorizedError
? "Wrong username or password."
: "Could not reach the server.",
);
} finally {
setBusy(false);
}
}
return (
<form onSubmit={submit}>
<input
name="username"
value={username}
onChange={(event) => setUsername(event.target.value)}
autoComplete="username"
/>
<input
name="password"
type="password"
value={password}
onChange={(event) => setPassword(event.target.value)}
autoComplete="current-password"
/>
<button type="submit" disabled={busy || !username || !password}>
{busy ? "Signing in…" : "Sign in"}
</button>
{error && <p role="alert">{error}</p>}
</form>
);
}The three outcomes
Prop
Type
What login() does behind the form
Worth knowing for two reasons — one benign, one that has caused a bug:
- It records that this sign-in was a form, in
localStorage. That marker is later used to resolve an ambiguous mode; see Auto-detection. - It temporarily disables impersonation for the duration of the exchange, and restores it in a
finally— so a failed sign-in no longer leaves user override permanently switched off, which it once did.