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.

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. 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 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. 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.