# Maru sign-in in your own app

> Let people sign in to your own website, web app or backend with their Maru account: register an app, run PKCE sign-in, verify identity on your server and save presets.

Canonical: https://maru.audio/docs/sign-in-in-your-app
Documentation index: https://maru.audio/llms.txt

> **Simpler on Maru.** If this is a Maru module, you don't need any of this.
> On Maru the account button, consent, presets and auto-connect come with
> `FaceModuleNavbar`, and `maru dev --sign-in` lets you build it offline.
> See [Maru sign-in in a module](https://maru.audio/docs/sign-in-in-a-module.md). It's free
> to start, and there's no callback, session or secret to manage.

Use this page when your product runs outside Maru and you want people to
connect their Maru account to it. Your app sends them to Maru to sign in and
approve, and gets back a session it owns.

## 1. Register your app

Open [Creator apps](https://maru.audio/account/apps) and create an app:

- **Name**: shown on the consent page ("Sign in to *your app*").
- **Callbacks**: the exact HTTPS addresses Maru may return to, such as
  `https://example.com/auth/maru/callback`. Fixed-port `http://localhost`
  callbacks are allowed for development.
- **Permissions**: start with identity. Add presets or generation only if you
  use them.

Copy the generated configuration. It contains public identifiers only, no
secret.

## 2. Sign people in from the browser

```ts
import {
  createCreatorAppSession,
  createCreatorAppAttemptStore,
} from "maruaudio/api"

const session = createCreatorAppSession({
  config, // from Creator apps: issuer, clientId, redirectUri, scopes
  mode: "browser",
  attemptStore: createCreatorAppAttemptStore(config, sessionStorage),
})

// On your "Sign in with Maru" button:
location.assign(await session.beginSignIn())

// On the exact registered callback page:
await session.completeSignIn(location.href)
history.replaceState(null, "", config.redirectUri)
```

Browser mode keeps tokens in memory only, so a reload signs out. That is
deliberate: tokens are never copied to `localStorage` or cookies for you.

## 3. Prefer a server session when you have a backend

With a backend, run the session in server mode. Keep one session per signed-in
browser, store its tokens server-side, and give the browser only your own
`HttpOnly`, `Secure`, `SameSite=Lax` cookie. Never send refresh credentials to
the browser. A server-mode session may also use a client secret from Creator
apps; Maru refuses a secret sent from a browser.

To check who is calling your API, verify the access token online:

```ts
import { verifyCreatorAppIdentity } from "maruaudio/api"

const who = await verifyCreatorAppIdentity({ issuer, clientId, accessToken })
// who.subject is stable for this person in your app only.
```

The subject is not an email or a global account id, and it differs between
apps. Decoding a token yourself is not verification.

## 4. Save presets (optional)

With the presets permissions and a registered module id:

```ts
import { createCreatorAppDataClient } from "maruaudio/api"

const app = createCreatorAppDataClient({ config, session })
const presets = await app.listPresets("com.example.my-synth")
await session.signOut() // or signOut({ revokeGrant: true }) to disconnect
```

## What people see

People approve your app once on Maru's consent page and can disconnect it in
[Connected apps](https://maru.audio/account/connections). Apps that only sign in and save presets
to an HTTPS callback can offer **Connect automatically next time**. Localhost
callbacks and apps that spend credits always ask.

## Related

- [Generation from your own server](https://maru.audio/docs/generation-from-your-server.md)
- [Add Maru sign-in to your product](https://maru.audio/help/creator-apps.md): the
  person's side of the connection.
- [Self-hosting overview](https://maru.audio/docs/self-hosting.md)
