Sign in with Metaloot

Metaloot OAuth lets browser games authenticate players with their existing Metaloot account. Metaloot still uses Clerk internally; your game only integrates with Metaloot as the identity provider.

Flow

  1. 1. Publish your game at /publish. You get a client ID, a one-time client secret, and a pre-filled integration prompt — also available later in the game's settings.
  2. 2. Add a server-side callback URL to the registered redirect URIs.
  3. 3. Integrate with the @metaloot/auth adapter — paste the settings prompt into your coding agent, or follow the adapter section below.
  4. 4. Players sign in with their Metaloot account; your game gets a stable player ID, and sign-ins show up in the game's analytics.

The adapter owns the whole OAuth flow for you. The protocol reference further down is only for stacks the adapter does not cover.

Agent Instructions

Published games get this prompt pre-filled with their client ID and redirect URI in their game settings. This generic version is the same prompt with placeholder values — replace them before pasting it into an agent.

Add Sign in with Metaloot to this game using the official @metaloot/auth adapter. Use the adapter — do not hand-roll the OAuth flow.

Metaloot app:
- Client ID: YOUR_METALOOT_CLIENT_ID
- Redirect URI: https://your-game.com/auth/metaloot/callback
- Game name: Your Game
- Docs: https://metaloot.app/docs/auth

Environment:
- Set METALOOT_CLIENT_ID="YOUR_METALOOT_CLIENT_ID"
- Set METALOOT_CLIENT_SECRET to this app's client secret on the server only. Never expose it in browser code.
- Set METALOOT_SESSION_SECRET to a long random server-only value.
- If the game uses a configurable callback URL, set METALOOT_REDIRECT_URI="https://your-game.com/auth/metaloot/callback"

Implementation requirements:
1. Install the latest version of @metaloot/auth using the project's package manager.
2. Wire the server adapter for the detected stack. The adapter must own:
   GET /auth/metaloot/start
   GET /auth/metaloot/callback
   GET /auth/metaloot/session
   GET or POST /auth/metaloot/logout
3. Mount the browser sign-in button with mountMetalootAuth from @metaloot/auth/browser.
4. On game boot, call /auth/metaloot/session and treat signedIn:true as the authenticated player.
5. Remove or bypass old guest-login, #mlt launch-token, or vendored Metaloot SDK logic.
6. After login, hide the sign-in button or replace it with the Metaloot player chip.
7. Keep the adapter's default cookie policy unless there is a specific host constraint. On HTTPS it sets SameSite=None; Secure so Metaloot portal iframes can read the game session.
8. Verify locally and in production that /auth/metaloot/session returns signedIn:true after a real Metaloot login.
9. Verify the embedded portal flow too: when a signed-in Metaloot player opens this game from Metaloot, the iframe should bootstrap through /auth/metaloot/start, create the game session cookie, and return without showing another sign-in button.

For a Node/Express/static game, use this shape:
import express from "express";
import { metalootAuth } from "@metaloot/auth/node";

app.use(metalootAuth({
  clientId: process.env.METALOOT_CLIENT_ID,
  clientSecret: process.env.METALOOT_CLIENT_SECRET,
  sessionSecret: process.env.METALOOT_SESSION_SECRET,
  redirectUri: process.env.METALOOT_REDIRECT_URI ?? "https://your-game.com/auth/metaloot/callback",
}));
app.use("/vendor/@metaloot/auth", express.static("node_modules/@metaloot/auth/dist"));

Browser mount:
<div id="metaloot-auth"></div>
<script type="module">
  import { mountMetalootAuth } from "/vendor/@metaloot/auth/browser.js";
  mountMetalootAuth("#metaloot-auth", { gameName: "Your Game" });
</script>

For a Next.js App Router game, create app/auth/metaloot/[...metaloot]/route.ts:
import { createMetalootRouteHandlers } from "@metaloot/auth/next";

export const { GET, POST } = createMetalootRouteHandlers({
  clientId: process.env.METALOOT_CLIENT_ID!,
  clientSecret: process.env.METALOOT_CLIENT_SECRET!,
  sessionSecret: process.env.METALOOT_SESSION_SECRET!,
  redirectUri: process.env.METALOOT_REDIRECT_URI ?? "https://your-game.com/auth/metaloot/callback",
});

Then mount @metaloot/auth/browser from a client component.

The Adapter

@metaloot/auth is the recommended integration. It owns the OAuth callback, signed game session, session endpoint, logout endpoint, and browser sign-in state so games do not implement OAuth themselves.

npm install @metaloot/auth@latest

Version 0.1.1 and newer keeps localhost development cookies simple, but sets HTTPS auth cookies as SameSite=None; Secure so Metaloot-hosted iframes can establish and read the game session.

Node or Express games can mount the auth routes before serving static files:

import express from "express";
import { metalootAuth } from "@metaloot/auth/node";

app.use(metalootAuth({
  clientId: process.env.METALOOT_CLIENT_ID,
  clientSecret: process.env.METALOOT_CLIENT_SECRET,
  sessionSecret: process.env.METALOOT_SESSION_SECRET,
  redirectUri: "https://your-game.com/auth/metaloot/callback",
}));
app.use("/vendor/@metaloot/auth", express.static("node_modules/@metaloot/auth/dist"));

Mount the browser control where the game should show sign-in:

<div id="metaloot-auth"></div>
<script type="module">
  import { mountMetalootAuth } from "/vendor/@metaloot/auth/browser.js";
  mountMetalootAuth("#metaloot-auth", { gameName: "Your Game" });
</script>

Protocol Reference

Everything below documents the raw OAuth 2.0 flow behind the adapter. Use it only when your stack cannot run @metaloot/auth — if you use the adapter, you are done and can skip this.

Authorize

GET https://metaloot.app/oauth/authorize

Token

POST https://metaloot.app/api/oauth/token

Userinfo

GET https://metaloot.app/api/oauth/userinfo

Authorization Request

Redirect the player from the game to Metaloot. Use a random state value and verify it in your callback.

https://metaloot.app/oauth/authorize?response_type=code&client_id=METALOOT_CLIENT_ID&redirect_uri=https%3A%2F%2Fyour-game.com%2Fauth%2Fmetaloot%2Fcallback&scope=profile%20email&state=RANDOM_STATE

Token Exchange

Exchange the authorization code from your backend only. Keep the client secret out of browser code.

curl -X POST https://metaloot.app/api/oauth/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=authorization_code" \
  -d "client_id=METALOOT_CLIENT_ID" \
  -d "client_secret=METALOOT_CLIENT_SECRET" \
  -d "code=CODE_FROM_CALLBACK" \
  -d "redirect_uri=https://your-game.com/auth/metaloot/callback"

Successful response:

{
  "access_token": "eyJ...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "scope": "profile email",
  "user": {
    "id": "user_...",
    "email": "player@example.com",
    "name": "Player Name",
    "image_url": "https://..."
  }
}

Load the Player

Use the access token to fetch the Metaloot player profile. The sub value is the stable Metaloot player ID for your game.

curl https://metaloot.app/api/oauth/userinfo \
  -H "Authorization: Bearer METALOOT_ACCESS_TOKEN"

Next.js Callback Example

import { cookies } from "next/headers";
import { NextResponse } from "next/server";

export async function GET(request: Request) {
  const url = new URL(request.url);
  const code = url.searchParams.get("code");
  const state = url.searchParams.get("state");
  const cookieStore = await cookies();

  if (!code || state !== cookieStore.get("metaloot_oauth_state")?.value) {
    return NextResponse.json({ error: "Invalid Metaloot callback" }, { status: 400 });
  }

  const response = await fetch("https://metaloot.app/api/oauth/token", {
    method: "POST",
    headers: { "Content-Type": "application/x-www-form-urlencoded" },
    body: new URLSearchParams({
      grant_type: "authorization_code",
      client_id: process.env.METALOOT_CLIENT_ID!,
      client_secret: process.env.METALOOT_CLIENT_SECRET!,
      code,
      redirect_uri: "https://your-game.com/auth/metaloot/callback",
    }),
  });

  const token = await response.json();
  if (!response.ok) {
    return NextResponse.json(token, { status: 400 });
  }

  cookieStore.set("metaloot_access_token", token.access_token, {
    httpOnly: true,
    secure: true,
    sameSite: "lax",
    path: "/",
    maxAge: token.expires_in,
  });

  return NextResponse.redirect(new URL("/", request.url));
}

Legacy: Hosted Widget

Before the adapter existed, games embedded a hosted sign-in widget and implemented the OAuth callback and session handling themselves. New integrations should use the adapter instead — only keep this if your game already has its own callback and session code.

<div id="metaloot-auth"></div>
<script src="https://metaloot.app/metaloot-auth.js"></script>
<script>
  MetalootAuth.mount("#metaloot-auth", {
    clientId: "YOUR_METALOOT_CLIENT_ID",
    redirectUri: "https://your-game.com/auth/metaloot/callback",
    gameName: "Your Game",
  });
</script>