Auth Helpers
@verse8/platform exposes a small Verse8 namespace that parses and
cryptographically verifies the ?auth=<token> credential the Verse8
host injects into game iframes. Use it when you need to read the current
user’s identity directly — for example, to display “Welcome, 0x123…” in
a header, or to read the user’s verseId for a non-VXShop API call.
VXShop.init() already consumes this token internally; you don’t need to
plumb these helpers into it manually. The helpers are here for the cases
where you want the values for your own UI / logic.
Install
The same Verse8 namespace ships across all three entry points. Pick the
one that matches your stack.
React app (default entry)
import { Verse8 } from "@verse8/platform";Vanilla / non-React (bundler)
import { Verse8 } from "@verse8/platform/vanilla";Script tag (no build step)
<script src="https://unpkg.com/@verse8/platform/dist/index.global.js"></script>
<script>
// `Verse8` is attached to `window` after the script loads —
// inline / deferred / module scripts that follow can use it directly.
const user = Verse8.getUser();
console.log(user.account);
</script>A jsdelivr mirror is also published — swap unpkg.com for
cdn.jsdelivr.net/npm/... if preferred. Pin a version (e.g.
@verse8/[email protected]) in production so a future release can’t shift
the bundle under you.
API
const Verse8 = {
/**
* Read `?auth=` from `window.location.search`, verify, and return the
* parsed user credential. Throws when missing / malformed / expired /
* signature mismatch.
*/
getUser(opts?: ParseAuthTokenOptions): UserCredential;
/**
* Verify + parse a raw authToken string (no URL access). Useful for
* server-side verification, tests, or scenarios where the token comes
* from somewhere other than the page URL.
*/
parseAuthToken(token: string, opts?: ParseAuthTokenOptions): UserCredential;
};
interface UserCredential {
verse: string;
account: `0x${string}`; // primary Verse8 EOA
exp: number; // unix seconds
sub?: { tier: string; exp: number } | null; // server-signed only
follow?: boolean; // server-signed only
externalWallet?: { // server-signed only
type: "abstractSmartWallet";
address: string; // e.g. user's Abstract Global Wallet
} | null;
}
interface ParseAuthTokenOptions {
/** Override the trusted Verse8 signer (defaults to the production address). */
trustedSigner?: `0x${string}` | string;
/** Skip the `exp` check (for tooling that replays historical tokens). */
ignoreExpiration?: boolean;
/**
* Reject self-signed (guest) tokens — accept only tokens signed by
* the trusted Verse8 signer, i.e. only *logged-in* users. Useful for
* surfaces that should only run for users with a Verse8 account.
*/
requireTrustedSigner?: boolean;
}Both helpers cache the verified credential per authToken string —
calling them repeatedly is cheap (signature recovery is the expensive
step). When the URL changes (different ?auth=), the cache invalidates
on the next call.
Verification model
The SDK accepts a token if the recovered signing address matches either:
- The Verse8 production signer — the logged-in path. The
Verse8 host issues the token from its server-side signing key after
the user signs in. Server-only fields (
sub,follow,externalWallet) are populated on this path. - The credential’s own
account— the guest / anonymous path. Users who haven’t signed in get a client-generated wallet; the client signs the credential JSON with that wallet, proving ownership of the address that appears asaccount.
Both paths produce a verified account you can trust. They differ on
which identity account represents:
- Logged-in user —
accountis the Verse8 EOA the user signed in with. The Verse8 server attests to it by signing the credential. - Guest user —
accountis the address of the wallet the client generated. The client attests to it by signing the credential. Different guest sessions get differentaccounts; there is no external identity behind it.
A page setting its own ?auth= can produce a valid self-signed
token, but only for an address whose private key it controls. It
cannot claim another user’s account without that user’s key. So
account is always a real key holder — only the logged-in path also
ties account to a Verse8 user identity.
Errors
Both helpers throw when verification fails. The error message begins
with [Verse8] and identifies the cause:
| Cause | Message contains |
|---|---|
No ?auth= query parameter (getUser only) | no auth query parameter found |
?auth= value is not valid base64 | is not valid base64 |
| Decoded payload is not valid JSON | is not valid JSON |
Outer envelope missing msg or signature | missing msg or signature |
Inner credential missing verse / account / exp | missing required fields |
exp is in the past | is expired |
| Signature doesn’t match trusted signer OR account | signature verification failed |
| Signature byte length wrong / non-hex | signature verification failed: ... |
Wrap in try/catch:
let user: UserCredential | null = null;
try {
user = Verse8.getUser();
} catch (err) {
console.error("[Verse8] no usable auth:", err.message);
// e.g. show a "please reload from the Verse8 portal" prompt
}Examples
Display the current user
import { Verse8 } from "@verse8/platform/vanilla";
const headerEl = document.querySelector("#user-account");
try {
headerEl.textContent = Verse8.getUser().account;
} catch {
headerEl.textContent = "guest";
}Script tag (no build step)
Plain HTML page, Unity WebGL host page, single-file prototype — load the
global build from a CDN and read window.Verse8 directly:
<!doctype html>
<span id="user-account">guest</span>
<script src="https://unpkg.com/@verse8/platform/dist/index.global.js"></script>
<script>
try {
const user = Verse8.getUser();
document.getElementById("user-account").textContent = user.account;
} catch (err) {
console.error("[Verse8]", err.message);
}
</script>Verse8 becomes available synchronously when the global script finishes
executing, so any inline <script> after it can call Verse8.getUser()
immediately. The global build also installs window.VXShop from the
same bundle — you don’t need a separate script tag for VXShop.
React
import { Verse8 } from "@verse8/platform";
import { useMemo } from "react";
function CurrentUser() {
const user = useMemo(() => {
try {
return Verse8.getUser();
} catch {
return null;
}
}, []);
return <span>{user?.account ?? "guest"}</span>;
}Inspect the full credential
const user = Verse8.getUser();
console.log(user.verse, user.account, new Date(user.exp * 1000));
if (user.sub) {
// Subscription info — only present on server-signed tokens.
console.log("subscription tier:", user.sub.tier);
}
if (user.externalWallet) {
// Secondary on-chain identity (e.g. Abstract Global Wallet).
// `account` stays the primary Verse8 EOA — externalWallet.address
// is for games that need the user's external chain identity.
console.log("external wallet:", user.externalWallet.address);
}Require a logged-in user (reject guests)
By default both paths are accepted — guest sessions get a valid
account, just without a tie to a Verse8 user identity. UX surfaces
that should only run for logged-in Verse8 users (anything reading
sub / follow / externalWallet, or granting entitlements tied to a
Verse8 account such as VXShop history) opt in to strict mode:
const user = Verse8.getUser({ requireTrustedSigner: true });When requireTrustedSigner is true, only tokens signed by the
trusted Verse8 signer pass; self-signed (guest) tokens throw with
[Verse8] signature verification failed: recovered signer ... is not the trusted Verse8 signer .... Catch it and route the user to a
sign-in prompt:
try {
const user = Verse8.getUser({ requireTrustedSigner: true });
showLoggedInUI(user);
} catch {
showLoginPrompt();
}Verify a token from a non-URL source
parseAuthToken lets you verify a token string directly — for example,
one received via postMessage, a WebSocket frame, or your own server
handing it back to the client:
import { Verse8 } from "@verse8/platform/vanilla";
window.addEventListener("message", (event) => {
if (event.data?.type !== "AUTH_REFRESH") return;
try {
const user = Verse8.parseAuthToken(event.data.token);
console.log("refreshed user:", user.account);
} catch (err) {
console.error("[Verse8] rejected refreshed token:", err);
}
});Custom trusted signer (staging, custom verse)
const user = Verse8.getUser({
trustedSigner: "0xMyCustomVerse8SignerAddress...",
});Token Format
For reference, the ?auth= token is
base64(JSON.stringify({ msg, signature })), where msg is either:
- The raw JSON
{ verse, account, exp, ... }, or - A base64 encoding of that JSON (the SDK handles both variants).
signature is a 65-byte hex string (0x + 130 hex chars) from
EIP-191 personal_sign over msg. The SDK recovers the signing
address from the signature and accepts the token only if it matches
the trusted Verse8 signer or the credential’s own account.
Bundle cost — signature verification adds ~7.3 KB gzipped to the
script-tag bundle (via @noble/secp256k1 + @noble/hashes). The ESM /
CJS entries leave both as peer deps so consumers’ bundlers can share
copies and tree-shake.