Platform
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. Every player gets a stable ID your game can key saves and profiles on.
How It Works#
- 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. Add a server-side callback URL to the registered redirect URIs.
- 3. Integrate with the @metaloot/auth adapter — paste the settings prompt into your coding agent, or follow the adapter section below.
- 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.
metaloot deploy) the auth endpoints and sign-in widget are provisioned automatically — see Hosting & Deploys. The adapter below is for games hosted elsewhere, and 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@latestVersion 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.
| GET https://metaloot.app/oauth/authorize | Authorization endpoint — send the player here. |
| POST https://metaloot.app/api/oauth/token | Token endpoint — exchange the authorization code server-side. |
| GET https://metaloot.app/api/oauth/userinfo | Userinfo endpoint — fetch the player profile with the access token. |
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_STATEToken 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>