Essentials

4. Connect to a BAQ server

In this guide we will add authentication UI and logic so we can connect our app to an actual BAQ server and use it with real data.

Code to follow this guide can be found on Github­ or StackBlitz­.

Here’s an overview of authentication in BAQ:

  1. Prompt the user for its entity (<Login> component).

  2. Register the app with the user’s server (SDK).

  3. Redirect to the authorization flow (<Login> component).

  4. The user authorizes the app and their server redirects back.

  5. The app extracts the authorization ID from the URL and can now start making requests against the server (<App> component).

We create a new Login.tsx component that will be displayed before our user is authenticated. All that we need to start the authentication process is a single text input to collect the user’s entity.

On “enter”, we return the entity to our parent component.

import {UnauthenticatedState} from "@baqhub/sdk-react";
import {FC, FormEvent} from "react";

interface LoginProps {
  state: UnauthenticatedState;
  onConnectClick: (entity: string) => void;
}

export const Login: FC<LoginProps> = props => {
  const {state, onConnectClick} = props;
  const isConnecting = state.connectStatus !== "idle";

  const onSubmit = (e: FormEvent<HTMLFormElement>) => {
    e.preventDefault();

    const form = e.currentTarget;
    const formData = new FormData(form);

    const entity = formData.get("entity")?.toString();
    if (!entity) {
      return;
    }

    onConnectClick(entity);
  };

  return (
    <div
      style={{
        display: "flex",
        flexDirection: "column",
        alignItems: "center",
        justifyContent: "center",
        width: "100%",
        height: "200px",
      }}
    >
      <h1>BAQ Todos</h1>
      <form
        style={{display: "flex", gap: "12px"}}
        onSubmit={onSubmit}
      >
        <input
          type="text"
          name="entity"
          placeholder="user.host.com"
          disabled={isConnecting}
          autoFocus
        />
        <button type="submit" disabled={isConnecting}>
          Sign in
        </button>
      </form>
    </div>
  );
};

What’s new:

  • UnauthenticatedState is the SDK’s authentication system state.

Once the app is registered with the user’s server, we need to navigate the browser to the provided URL for the authorization flow to begin.

-import {FC, FormEvent} from "react";
+import {FC, FormEvent, useEffect} from "react";

export const Login: FC<LoginProps> = props => {
  const {state, onConnectClick} = props;
  const isConnecting = state.connectStatus !== "idle";

+  useEffect(() => {
+    if (state.connectStatus !== "waiting_on_flow") {
+      return;
+    }
+
+    window.location.href = state.flowUrl;
+  }, [state]);
  ...

Finally, we tie it all together in App.tsx by displaying our <Login> component when not authenticated, or the app UI otherwise.

We wrap the app in a <Store> component that will be managing our records and communicating with the BAQ server. This is required for the hooks we added in step 3­ to work properly.

import {FC} from "react";
import {Header} from "./components/Header";
import {TaskList} from "./components/TaskList";
+import {Login} from "./components/Login";
+import {localStorageAdapter} from "@baqhub/sdk-react-dom";
+import {buildAuthentication} from "./baq/authentication";
+import {Store} from "./baq/store";

+// Build the redirect URL from the current location.
+const redirectUrl =
+  window.location.origin + "?authorization_id={authorization_id}";

+// Pick a storage provider for authentication data (here localStorage).
+const {useAuthentication} = buildAuthentication({
+  storage: localStorageAdapter,
+  redirectUrl,
+});

+// Read the "authorization_id" from the query params.
+const currentUrl = new URL(window.location.href);
+const authorizationId = currentUrl.searchParams.get("authorization_id");

export const App: FC = () => {
+  // Find the current authentication state.
+  const {state, onConnectRequest, onDisconnectRequest} = useAuthentication(
+    {authorizationId}
+  );

+  // If not authenticated, display the login page.
+  if (state.status === "unauthenticated") {
+    return <Login state={state} onConnectClick={onConnectRequest} />;
+  }

+  // Otherwise, display the app.
  return (
+    <Store identity={state.identity} onDisconnectRequest={onDisconnectRequest}>
      <section className="todoapp">
        <Header />
        <TaskList />
      </section>
+    </Store>
  );
};

What’s new?

  • buildAuthentication configures the authentication system.

  • localStorageAdapter lets the SDK persist data in localStorage.

  • redirectUrl will be used to redirect back to the app.

  • authorizationId is received when the auth flow completes.

  • useAuthentication manages the current authentication state.

  • Store manages records and keeps them in sync with the server.

That’s it! The app is now ready to authenticate with a BAQ server and use it to store its data. To give it a try, create a free account on BAQ.RUN­ and start the dev server if it’s not already running:

npm run dev

Navigating to http://localhost:5173/ displays the login page:

The app login page.

After going through the authorization flow with your newly created BAQ account, you should now be able to create tasks, mark them as completed, and delete them.

Things to try:

  • Refresh the app. All your data is still here!

  • Open two windows. State is synchronized between instances.

The app is now fully functional and can be deployed for public use.

For a more in-depth look, follow up with these helpful guides:

© 2024 Quentez