> ## Documentation Index
> Fetch the complete documentation index at: https://docs.neuro-tech.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Try Quick Login

> Scan a real QR code and inspect the identity returned after approval

export const QuickLoginDemo = () => {
  const [attempt, setAttempt] = useState(0);
  const [state, setState] = useState("idle");
  const [code, setCode] = useState(null);
  const [result, setResult] = useState(null);
  const [error, setError] = useState("");
  const [remaining, setRemaining] = useState(300);
  useEffect(() => {
    if (!attempt) return;
    let active = true;
    let socket;
    let refresh;
    let ping;
    let deadline;
    let connectTimeout;
    let countdown;
    const controller = new AbortController();
    const expires = Date.now() + 300000;
    const stop = () => {
      active = false;
      clearTimeout(refresh);
      clearTimeout(deadline);
      clearTimeout(connectTimeout);
      clearInterval(ping);
      clearInterval(countdown);
      controller.abort();
      if (socket) {
        socket.onopen = null;
        socket.onmessage = null;
        socket.onerror = null;
        socket.onclose = null;
        if (socket.readyState === 1) socket.send(JSON.stringify({
          cmd: "Unregister"
        }));
        socket.close();
      }
    };
    const fail = message => {
      if (!active) return;
      stop();
      setCode(null);
      setError(message);
      setState("error");
    };
    const requestCode = async tab => {
      try {
        const response = await fetch("https://eu.id.tagroot.io/QuickLogin", {
          method: "POST",
          headers: {
            "Content-Type": "application/json"
          },
          credentials: "omit",
          cache: "no-store",
          signal: controller.signal,
          body: JSON.stringify({
            serviceId: "",
            tab,
            mode: "base64",
            purpose: "Display my Legal Identity and returned identity information on the Neuro developer documentation Quick Login demo page. This request is valid for five minutes."
          })
        });
        if (!response.ok) throw new Error(`QR request returned HTTP ${response.status}.`);
        const data = await response.json();
        if (!active) return;
        if (data.contentType !== "image/png" || typeof data.base64 !== "string" || !(/^[A-Za-z0-9+/=\r\n]+$/).test(data.base64) || typeof data.signUrl !== "string" || !(/^tagsign:[^\s]+$/i).test(data.signUrl)) {
          throw new Error("The service returned an unsupported QR response.");
        }
        setCode(data);
        setState("waiting");
        refresh = setTimeout(() => requestCode(tab), 2000);
      } catch (cause) {
        if (active) fail(cause instanceof TypeError ? "Could not reach the QR service. Check your connection and the provider's CORS settings." : cause.message || "The QR request failed.");
      }
    };
    setState("connecting");
    setCode(null);
    setResult(null);
    setError("");
    setRemaining(300);
    try {
      const tab = crypto.randomUUID();
      socket = new WebSocket("wss://eu.id.tagroot.io/ClientEventsWS", ["ls"]);
      connectTimeout = setTimeout(() => fail("The event connection timed out. Try again or open the provider's hosted example."), 15000);
      deadline = setTimeout(() => {
        if (!active) return;
        stop();
        setCode(null);
        setState("expired");
        setRemaining(0);
      }, 300000);
      countdown = setInterval(() => setRemaining(Math.max(0, Math.ceil((expires - Date.now()) / 1000))), 1000);
      socket.onopen = () => {
        if (!active) return;
        clearTimeout(connectTimeout);
        socket.send(JSON.stringify({
          cmd: "Register",
          tabId: tab,
          location: window.location.href
        }));
        ping = setInterval(() => {
          if (socket.readyState === 1) socket.send(JSON.stringify({
            cmd: "Ping"
          }));
        }, 10000);
        requestCode(tab);
      };
      socket.onmessage = ({data}) => {
        if (!active || !data) return;
        try {
          const event = JSON.parse(data);
          if (event.type !== "SignatureReceived") return;
          if (!event.data || typeof event.data !== "object" || Array.isArray(event.data) || typeof event.data.Id !== "string") {
            fail("The service returned an unexpected identity response.");
            return;
          }
          stop();
          setCode(null);
          setResult(event.data);
          setState("complete");
        } catch {
          fail("The event service returned an unreadable response.");
        }
      };
      socket.onerror = () => fail("Could not connect to the event service. Your network or the provider may block WebSocket connections.");
      socket.onclose = () => fail("The event connection closed before approval. Start a new attempt.");
    } catch {
      fail("Quick Login could not start. Use a browser with WebSocket support on HTTPS or localhost.");
    }
    return stop;
  }, [attempt]);
  const running = state === "connecting" || state === "waiting";
  const buttonStyle = {
    padding: "8px 14px",
    borderRadius: 6,
    border: "1px solid #808080",
    font: "inherit",
    cursor: "pointer"
  };
  const clear = () => {
    setAttempt(0);
    setState("idle");
    setCode(null);
    setResult(null);
    setError("");
  };
  return <div className="not-prose" style={{
    border: "1px solid #808080",
    borderRadius: 8,
    padding: 20,
    margin: "24px 0",
    minWidth: 0
  }}>
      <div style={{
    display: "flex",
    flexWrap: "wrap",
    alignItems: "center",
    gap: 12,
    justifyContent: "space-between"
  }}>
        <strong>Quick Login · EU Neuron</strong>
        <span role="status" aria-live="polite">
          {state === "idle" && "Ready"}
          {state === "connecting" && "Connecting..."}
          {state === "waiting" && "Waiting for approval"}
          {state === "complete" && "Identity received"}
          {state === "expired" && "Attempt expired"}
          {state === "cancelled" && "Cancelled"}
          {state === "error" && "Connection failed"}
        </span>
      </div>
      {running && <p style={{
    fontVariantNumeric: "tabular-nums"
  }}>{Math.floor(remaining / 60)}:{String(remaining % 60).padStart(2, "0")} remaining</p>}
      {code && <a href={code.signUrl} aria-label="Open Quick Login in your identity app" style={{
    display: "block",
    width: 280,
    maxWidth: "100%",
    margin: "20px auto",
    background: "white",
    padding: 12,
    borderRadius: 4
  }}>
          <img src={`data:image/png;base64,${code.base64}`} alt="Quick Login QR code" width={280} height={280} style={{
    display: "block",
    width: "100%",
    height: "auto",
    aspectRatio: "1 / 1",
    margin: 0
  }} />
        </a>}
      {error && <p role="alert" style={{
    overflowWrap: "anywhere"
  }}>{error}</p>}
      {result && <pre aria-label="Returned identity JSON" tabIndex={0} style={{
    maxHeight: 480,
    overflow: "auto",
    whiteSpace: "pre-wrap",
    overflowWrap: "anywhere",
    fontSize: 13,
    lineHeight: 1.6,
    padding: 16,
    background: "rgba(128,128,128,0.1)",
    borderRadius: 6
  }}>{JSON.stringify(result, null, 2)}</pre>}
      <div style={{
    display: "flex",
    flexWrap: "wrap",
    gap: 12,
    marginTop: 20
  }}>
        {!running && <button type="button" style={buttonStyle} onClick={() => setAttempt(value => value + 1)}>{state === "idle" ? "Start Quick Login" : "Start again"}</button>}
        {running && <button type="button" style={buttonStyle} onClick={() => {
    setAttempt(0);
    setCode(null);
    setState("cancelled");
  }}>Cancel</button>}
        {result && <button type="button" style={buttonStyle} onClick={clear}>Clear result</button>}
      </div>
    </div>;
};

Use Neuro Access or a compatible identity app to scan the code, or open it on the same device. Review the purpose before approving.

<Note>
  This demo connects to `eu.id.tagroot.io`. Approving shares your identity with this browser page and displays the returned JSON, which may include personal information and attachment access URLs. The demo keeps the result in memory until you clear it or leave the page. It does not sign you into the documentation or request an Agent API token.
</Note>

<QuickLoginDemo />

## Read the result

The QR response starts the request. After approval, the `SignatureReceived` event supplies the actual identity. Inspect `Id`, `Provider`, `State`, `Properties`, and `Attachments` using the [response reference](/quick-login/identity-response).

The demo waits for up to five minutes and refreshes its QR while waiting. Cancel stops this page from listening; issued codes remain subject to the service's expiry. A new attempt uses a new tab identifier.

If your network blocks the connection, use the [provider's hosted example](https://eu.quicklog.in/QuickLogin.md) or follow [troubleshooting](/quick-login/troubleshooting). To add the flow to your application, start with the [browser quickstart](/quick-login/quickstart).
