# Maru sign-in in a module

> Let people sign in to your module with their Maru account, save presets to it and reach your module's Maru pages, with one account button the SDK adds for you.

Canonical: https://maru.audio/docs/sign-in-in-a-module
Documentation index: https://maru.audio/llms.txt

A musician opens your reverb on its Maru page and clicks the round account
button in the module's header. A Maru window asks once whether to connect your
module to their account. After that, the button shows they are signed in, and
its menu saves and loads their own presets, opens your module's page and lists
your other modules. Your face never sees a password, token or email.

This page shows how to turn that on, try it locally and use the account in your
own controls.

## How it fits together

Sign-in is a **host capability**, like generation or file import. The host that
runs your face (your module's Maru page, Studio's preview) owns the account and
the sign-in window. Your face receives `face.appAuth`: who is signed in (as an
opaque id), and a few actions it can ask for.

| Part | Owner | What it does |
| --- | --- | --- |
| Your module's **app** | Maru, one registration per module | The name people see when they connect, and what it may do (identity, presets, optionally generation) |
| `face.appAuth` | The host | Sign in, sign out, list/save/load the person's presets for this module, open Maru pages |
| `ModuleFaceAccount` | The SDK (`maruaudio/ui/module-face-controls`) | The account button and its menu, drawn in your face's own colors |

Hosts without accounts, such as an offline native plug-in, omit `face.appAuth`.
The button then does not appear, and the rest of your module works as before.

## 1. Try it locally

Start the workbench with a simulated account:

```sh
maru dev --sign-in
```

(Or add `?sign-in=1` to an open workbench tab.) The face gets the same
`face.appAuth` it gets on Maru, backed by a local test account. Sign-in takes a
moment so you can see the waiting state. Presets you save stay in this browser.
Nothing is sent to Maru.

If your face uses `FaceModuleNavbar`, the account button is already in its
header. There is nothing to add.

## 2. Turn it on for real

Publish your module:

```sh
maru publish
```

When your account has a handle, the module gets its own address,
`https://<name>.maru.page`, and a Maru app for sign-in and presets is
registered for it automatically. Check it with:

```sh
maru module app status
```

It reports `faces: sign-in only` for an app that signs in and saves presets
(`ready` once generation is enabled too). You can rename the app or change its
access in [Creator apps](https://maru.audio/account/apps).

To let the module **generate with each person's credits** as well, run
`maru module app enable`. See
[Add generation to an instrument](https://maru.audio/docs/generative-features.md#add-generation-to-an-instrument).
Spending needs the person's explicit approval each time they connect, so a
generation app never connects automatically (see below).

## 3. Place the button yourself (optional)

`FaceModuleNavbar` adds the button to its actions. With your own header, render
it where you want it:

```tsx
import type { AudioModuleFaceProps } from "maruaudio/contracts"
import { ModuleFaceAccount } from "maruaudio/ui/module-face-controls"

export default function Face(face: AudioModuleFaceProps) {
  return (
    <header className="my-header">
      <h1>MY SYNTH</h1>
      {face.appAuth ? (
        <ModuleFaceAccount
          key={`${face.appAuth.clientId}:${face.appAuth.instanceId}`}
          service={face.appAuth}
        />
      ) : null}
    </header>
  )
}
```

The button uses `currentColor`, so it follows your header's text color in
light and dark designs.

What the button shows:

| State | Button | Menu |
| --- | --- | --- |
| Signed out | Person icon | One line and **Sign in with Maru** |
| Waiting for the Maru window | Spinner (hover: "Waiting for Maru sign-in…") | The same spinner on the button |
| Just signed in | Check mark, briefly | Account menu |
| Signed in | Person icon with a status dot | **Your presets**, save field, **Plugin page on Maru**, **More from this creator**, **Connected apps**, **Sign out** |

## 4. Use the account in your own controls

`face.appAuth` is a plain object. For example, a save button that asks for
sign-in only when it is needed:

```tsx
import { useState } from "react"
import type { AudioModuleFaceProps } from "maruaudio/contracts"

export function SaveToAccount({ face }: { face: AudioModuleFaceProps }) {
  const account = face.appAuth
  const [note, setNote] = useState("")
  if (!account) return null // this host has no accounts

  return (
    <div>
      <button
        onClick={async () => {
          try {
            if (!account.subject) await account.signIn()
            await account.savePreset("My sound")
            setNote("Saved to your Maru account.")
          } catch {
            setNote("Not saved. Try again.")
          }
        }}
      >
        Save to my account
      </button>
      <span role="status">{note}</span>
    </div>
  )
}
```

| Member | What it does |
| --- | --- |
| `subject` | `null` when signed out; otherwise a stable id for this person **in your app only**. It is not their Maru account id and differs between apps. |
| `signIn()` | Opens Maru's sign-in window. Resolves when the person has connected; rejects if they close it. Call it from a click. |
| `signOut()` | Signs out of your module on this host. |
| `listPresets()` | The person's saved presets for this module: `{ id, name }[]`. |
| `savePreset(name)` | Saves the module's current sound. The host captures the state; you pass only a name. |
| `loadPreset(id)` | Restores a saved preset through the host. |
| `pages`, `openPage(page)` | The Maru pages this host can open: `"module"`, `"creator"`, `"connections"`. You name a page; the host opens its real address in a new tab. |

A new object arrives whenever the account changes, so read `face.appAuth` from
props on each render rather than storing it.

## Connecting automatically

The first time someone signs in, Maru shows a consent page with your app's
name and what it may do. For an app that only signs in and saves presets, that
page also offers **Connect automatically next time**. If they turn it on, later
sign-ins go straight back to your module. They can turn this off in
[Connected apps](https://maru.audio/account/connections) with **Ask every time**, or disconnect
your app entirely.

It is never offered when the app can spend credits or activate licenses, or
when sign-in returns to a local address, such as a development server on
`localhost`.

## Rules the host enforces

- Your face never receives a password, token, cookie, email or account id, and
  cannot read them from the host.
- The face cannot open arbitrary links. It names a page; the host decides the
  address.
- One enabled app per module. With none, or with several, hosts offer no
  sign-in.
- Every request is checked against the current app and signed-in person, so a
  stale or forged request from the face is ignored.

## Running outside Maru

If your product runs on your own site, backend or app instead of a Maru host,
you run the sign-in yourself; see
[Maru sign-in in your own app](https://maru.audio/docs/sign-in-in-your-app.md).
On Maru, none of that is needed.

## Related

- [Add Maru sign-in to your product](https://maru.audio/help/creator-apps.md):
  what people see when they connect, and app permissions.
- [Generation in an instrument](https://maru.audio/docs/generative-features.md#add-generation-to-an-instrument):
  `face.appGeneration`, which uses the same app.
- [Module UI](https://maru.audio/docs/ui.md): `FaceModuleNavbar` and the rest of the face kit.
