MojoWare
MarkdownOpen in ClaudeOpen in ChatGPT

Identity & authentication

Use the public device ID plus a signed product credential to sign a kiosk into your own backend. Your product decides which devices it accepts and issues its own application session.

Start here · Complete Markdown

Get a credential: getAuthKey

async function loginKiosk() {
  const bridge = window.mojoware;
  if (!bridge) throw new Error("Managed kiosk bridge unavailable");
  await bridge.ready;

  const device_id = bridge.device_id;
  const auth_key = await bridge.getAuthKey();

  // Example route owned by YOUR product, not a MojoWare endpoint.
  const response = await fetch("/api/device/login", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ device_id, auth_key })
  });
  if (!response.ok) throw new Error("Kiosk login failed");
  return response.json();
}

With no argument, getAuthKey() uses the current monitor's approved WebApp audience. This is preferred: the page does not need to guess or hard-code it.

getAuthKey("wmwx") is also supported only when wmwx is that assigned app's configured audience. An explicit null, empty, invalid, or mismatched audience is rejected. The browser cannot request a different app's identity.

What the credential means

Native management keys, kiosk management JWTs, and signing private keys are never returned by the bridge.

Verify on your server

For online verification, your backend calls the kiosk's regional MojoWare API:

POST /api/kiosk/identity/verify
Authorization: apikey <TENANT_SCOPED_SERVER_API_KEY>
Content-Type: application/json

{
  "device_id": "<PUBLIC_DEVICE_ID>",
  "auth_key": "<MWK1_CREDENTIAL_FROM_THE_DEVICE>",
  "audience": "<EXPECTED_CONFIGURED_APP_AUDIENCE>"
}

Use the regional API assigned to your integration; the US API is https://api.mojoware.net. Keep the verifier API key on your server. It needs verify_kiosk_attestation for the kiosk's current group. Choose the expected audience from trusted server configuration, not an unvalidated value submitted by the client.

Online verification checks the registered signing key, current tenant/assignment, business eligibility, current configured audience, and credential revision.

Successful response:

{
  "status": true,
  "data": {
    "ok": true,
    "device_id": "<PUBLIC_DEVICE_ID>",
    "sn": "<PUBLIC_DEVICE_ID>",
    "assignment_version": 7,
    "state": "active",
    "group_id": 123,
    "credential_version": 1
  }
}

An invalid credential returns data: { "ok": false, "reason": "invalid_auth_key" }. Permission or transport failures can also occur. Check both the HTTP outcome and data.ok === true; HTTP 200 or status: true alone is not verification.

After verification, your backend still checks its own authorized device association and issues its own session. Do not automatically enroll an unknown device merely because it supplies a valid-looking ID or token.

A local verifier needs a trusted, previously associated public key or an authorized credential pin. Never trust a public key supplied alongside the login request. Local signature verification alone cannot discover a cloud reset, revocation, or reassignment; your product also owns revocation of sessions it has already issued.

Fresh challenge proofs: getAuthProof

Use this flow when your backend needs a fresh, single-use challenge rather than the stable bearer:

const bridge = await getKioskBridge(); // Helper from Start here.
const proof = await bridge.getAuthProof({
  nonce: challengeFromYourBackend,
  audience: expectedConfiguredAudience
});
// Send proof to your backend over HTTPS.

The nonce is a server-generated string of 16–256 characters. The audience must match the assigned app. The native proof expires after 30 seconds and returns { payload, signature }.

Your backend calls the same verifier using the separate proof mode:

const verificationBody = {
  proof, // The complete { payload, signature } returned by the bridge.
  expected_nonce: issuedNonce,
  audience: expectedConfiguredAudience,
  origin: "https://your-app.example"
};

Send this body from your backend with its tenant-scoped verifier API key. Do not mix auth_key or device_id fields into this proof-mode request. Check data.ok, atomically consume the issued nonce on your backend, and reject reuse. The public device ID alone is never authentication.

Token format for implementers

The stable token payload has exactly v, device_id, aud, key_id, and credential_version. It is sorted, compact UTF-8 JSON encoded as canonical unpadded base64url. Ed25519 signs the ASCII bytes mwk1.<encoded-payload>, including the prefix. The signature is also canonical unpadded base64url.

Challenge payloads instead contain v, device_id, sn, key_id, assignment_version, nonce, aud, origin, iat, exp; their Ed25519 signature covers sorted compact UTF-8 JSON. Do not use the stable-token signing format for a challenge proof.