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

# JavaScript/TypeScript SDK

> A lean, type-safe, `fetch`-based API client for the Hanko Passkey API

The `@teamhanko/passkeys-sdk` package lets you call the Hanko Passkey API from JavaScript/TypeScript that supports `fetch`.

This includes Node.js, Browsers, Deno, Bun, and frameworks like Next.js, Astro, and SvelteKit.

<CodeGroup>
  ```bash npm theme={null}
  npm i @teamhanko/passkeys-sdk
  ```

  ```bash yarn theme={null}
  yarn add @teamhanko/passkeys-sdk
  ```

  ```bash bun theme={null}
  bun add @teamhanko/passkeys-sdk
  ```

  ```bash pnpm theme={null}
  pnpm add @teamhanko/passkeys-sdk
  ```
</CodeGroup>

## Usage

A `tenant` is an API client instance for one tenant of the Hanko Passkey API.

<Accordion title="See multitenancy">
  If the app you’re building supports organizations, teams, or anything similar, you will likely have heard of multitenancy.

  The Passkey API supports multitenancy as well — you can create tenants (a.k.a. organizations, teams, …) and add the passkeys of users exclusively to those tenants. Then your users will only be able to log in with passkeys specifically for that tenant.

  For example: In your app, a user is part of two teams: the “ACME Corp.” and the “A-Team.”

  They create a passkey to log into “ACME Corp.”

  That passkey will only work for the “ACME Corp.”, not the “A-Team”.
</Accordion>

If you use Hanko Cloud, get your tenant ID and API key from the [dashboard](https://cloud.hanko.io/organizations).

Create a new tenant instance:

```ts theme={null}
const passkeyApi = tenant({
  tenantId: "<your tenant id>",
  apiKey: "<your secret api key>",
});
```

* If you only use public API methods, like `/login/initialize`, you can omit the `apiKey`.
* If you're self-hosting the Passkey API, make sure to pass the `baseUrl` as well.

Now you're ready to call the API. For example, to start the process of registering a new passkey:

```ts theme={null}
const creationOptions = await tenant.registration.initialize({
  userId: "<id of the user in your database>",
  username: "<username of the user>",
});
```

## Example Implementation

<Steps>
  <Step title="Install the SDK">
    <CodeGroup>
      ```bash npm theme={null}
      npm i @teamhanko/passkeys-sdk
      ```

      ```bash yarn theme={null}
      yarn add @teamhanko/passkeys-sdk
      ```

      ```bash bun theme={null}
      bun add @teamhanko/passkeys-sdk
      ```

      ```bash pnpm theme={null}
      pnpm add @teamhanko/passkeys-sdk
      ```
    </CodeGroup>
  </Step>

  <Step title="Get your tenant ID and API key">
    Get your tenant ID and API key from [Hanko Cloud](https://cloud.hanko.io/) and add them to your `.env` file.

    ```bash .env theme={null}
    PASSKEYS_API_KEY=your-api-key
    PASSKEYS_TENANT_ID=your-tenant-id
    ```
  </Step>

  <Step title="Allow users to register passkeys as a login method">
    On your backend, you’ll have to call `tenant({ ... }).registration.initialize()` and `registration.finalize()` to create and store a passkey.

    ```js services.js theme={null}
    import { tenant } from "@teamhanko/passkeys-sdk";
    import dotenv from "dotenv";
    import db from "../db.js";

    dotenv.config();

    const passkeyApi = tenant({
      apiKey: process.env.PASSKEYS_API_KEY,
      tenantId: process.env.PASSKEYS_TENANT_ID,
    });

    async function startServerPasskeyRegistration(userID) {
      const user = db.users.find((user) => user.id === userID);

      const createOptions = await passkeyApi.registration.initialize({
        userId: user.id,
        username: user.email || "",
      });

      return createOptions;
    }

    async function finishServerPasskeyRegistration(credential) {
      await passkeyApi.registration.finalize(credential);
    }
    ```

    ```js controllers.js theme={null}
    async function handlePasskeyRegister(req, res) {
      const { user } = req;
      const userID = user.id;

      if (!userID) {
        return res.status(401).json({ message: "Unauthorized" });
      }
      console.log("userId", userID);

      const { start, finish, credential } = req.body;

      try {
        if (start) {
          const createOptions = await startServerPasskeyRegistration(userID);
          console.log("registration start");
          return res.json({ createOptions });
        }
        if (finish) {
          await finishServerPasskeyRegistration(credential);
          return res.json({ message: "Registered Passkey" });
        }
      } catch (error) {
        return res.status(500).json(error);
      }
    }
    ```

    **Frontend**

    On your frontend, the `registerPasskey()` function handles the passkey registration process. It first sends a request to the server to initiate the registration process and receives the response for creating a new passkey.

    It then uses the `@github/webauthn-json` library to create a new passkey credential based on the received options from the response. Finally, it sends another request to the server with the newly created credential to complete the registration process.

    ```tsx theme={null}
      async function registerPasskey() {
            const createOptionsResponse = await fetch("http://localhost:5001/api/passkeys/register", {
                method: "POST",
                headers: { "Content-Type": "application/json" },
                credentials: 'include',
                body: JSON.stringify({ start: true, finish: false, credential: null }),
            });

            const { createOptions } = await createOptionsResponse.json();
            console.log("createOptions", createOptions)

            const credential = await create(
                createOptions as CredentialCreationOptionsJSON,
            );
            console.log(credential)

            const response = await fetch("http://localhost:5001/api/passkeys/register", {
                method: "POST",
                headers: { "Content-Type": "application/json" },
                credentials: "include",
                body: JSON.stringify({ start: false, finish: true, credential }),
            });
            console.log(response)

            if (response.ok) {
                toast.success("Registered passkey successfully!");
                return;
            }
        }
    ```
  </Step>

  <Step title="Allow users to log in with passkeys">
    ```js services.js theme={null}
    async function startServerPasskeyLogin() {
      const options = await passkeyApi.login.initialize();
      return options;
    }

    async function finishServerPasskeyLogin(options) {
      const response = await passkeyApi.login.finalize(options);
      return response;
    }
    ```

    ```js controllers.js theme={null}
    async function handlePasskeyLogin(req, res) {
      const { start, finish, options } = req.body;

      try {
        if (start) {
          const loginOptions = await startServerPasskeyLogin();
          return res.json({ loginOptions });
        }
        if (finish) {
          const jwtToken = await finishServerPasskeyLogin(options);
          const userID = await getUserID(jwtToken?.token ?? "");
          console.log("userID from hanko", userID);
          const user = db.users.find((user) => user.id === userID);
          if (!user) {
            return res.status(401).json({ message: "Invalid user" });
          }
          console.log("user", user);
          const sessionId = uuidv4();
          setUser(sessionId, user);
          res.cookie("sessionId", sessionId);
          return res.json({ message: " Passkey Login successful" });
        }
      } catch (error) {
        console.error(error);
        return res
          .status(500)
          .json({ message: "An error occurred during the passke login process." });
      }
    }
    ```

    **Frontend**

    ```tsx theme={null}
        async function signInWithPasskey() {
            const createOptionsResponse = await fetch("http://localhost:5001/api/passkeys/login", {
                method: "POST",
                headers: { "Content-Type": "application/json" },
                credentials: 'include',
                body: JSON.stringify({ start: true, finish: false, credential: null }),
            });

            const { loginOptions } = await createOptionsResponse.json();

            // Open "register passkey" dialog
            const options = await get(
                loginOptions as any,
            );

            const response = await fetch("http://localhost:5001/api/passkeys/login", {
                method: "POST",
                headers: { "Content-Type": "application/json" },
                credentials: 'include',
                body: JSON.stringify({ start: false, finish: true, options }),
            });

            if (response.ok) {
                console.log("user logged in with passkey")
                navigate("/dashboard")
                return;
            }
        }
    ```
  </Step>
</Steps>

## Try it yourself

Check out sample apps made using the SDK:

<CardGroup cols={2}>
  <Card
    title="React-Express Example"
    href="https://github.com/teamhanko/passkeys-react-express"
    icon={
  <svg
    xmlns="http://www.w3.org/2000/svg"
    className="icon icon-tabler icon-tabler-brand-react"
    width="30"
    height="30"
    viewBox="0 0 30 30"
    strokeWidth="2"
    stroke="#5465FF"
    fill="none"
    strokeLinecap="round"
    strokeLinejoin="round"
  >
    <path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
    <path d="M6.306 8.711c-2.602 .723 -4.306 1.926 -4.306 3.289c0 2.21 4.477 4 10 4c.773 0 1.526 -.035 2.248 -.102"></path>
    <path d="M17.692 15.289c2.603 -.722 4.308 -1.926 4.308 -3.289c0 -2.21 -4.477 -4 -10 -4c-.773 0 -1.526 .035 -2.25 .102"></path>
    <path d="M6.305 15.287c-.676 2.615 -.485 4.693 .695 5.373c1.913 1.105 5.703 -1.877 8.464 -6.66c.387 -.67 .733 -1.339 1.036 -2"></path>
    <path d="M17.694 8.716c.677 -2.616 .487 -4.696 -.694 -5.376c-1.913 -1.105 -5.703 1.877 -8.464 6.66c-.387 .67 -.733 1.34 -1.037 2"></path>
    <path d="M12 5.424c-1.925 -1.892 -3.82 -2.766 -5 -2.084c-1.913 1.104 -1.226 5.877 1.536 10.66c.386 .67 .793 1.304 1.212 1.896"></path>
    <path d="M12 18.574c1.926 1.893 3.821 2.768 5 2.086c1.913 -1.104 1.226 -5.877 -1.536 -10.66c-.375 -.65 -.78 -1.283 -1.212 -1.897"></path>
    <path d="M11.5 12.866a1 1 0 1 0 1 -1.732a1 1 0 0 0 -1 1.732z"></path>
  </svg>
}
  >
    Full source code available on our GitHub.
  </Card>

  <Card
    title="Remix example"
    href="https://github.com/teamhanko/passkeys-remix"
    icon={
  <svg
    xmlns="http://www.w3.org/2000/svg"
    className="icon icon-tabler icon-tabler-brand-github"
    width="24"
    height="24"
    viewBox="0 0 24 24"
    strokeWidth="2"
    stroke="#5465FF"
    fill="none"
    strokeLinecap="round"
    strokeLinejoin="round"
  >
    <path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
    <path d="M9 19c-4.3 1.4 -4.3 -2.5 -6 -3m12 5v-3.5c0 -1 .1 -1.4 -.5 -2c2.8 -.3 5.5 -1.4 5.5 -6a4.6 4.6 0 0 0 -1.3 -3.2a4.2 4.2 0 0 0 -.1 -3.2s-1.1 -.3 -3.5 1.3a12.3 12.3 0 0 0 -6.2 0c-2.4 -1.6 -3.5 -1.3 -3.5 -1.3a4.2 4.2 0 0 0 -.1 3.2a4.6 4.6 0 0 0 -1.3 3.2c0 4.6 2.7 5.7 5.5 6c-.6 .6 -.6 1.2 -.5 2v3.5"></path>
  </svg>
}
  >
    Full source code available on our GitHub.
  </Card>
</CardGroup>
