--- title: "Identity & authentication" description: "Use window.mojoware to identify a kiosk, get an app-scoped credential, and verify it on your backend." canonical: "https://mojoware.net/docs/kiosk/authentication/" --- # 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](/docs/kiosk/home/) · [Complete Markdown](/docs/kiosk/llms-full.txt) ## Get a credential: getAuthKey ```js 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 - Format: `mwk1..`. - The credential belongs to one **device + application audience + credential revision**. It is not a fleet-wide API key. - It is stable across reloads, reboots, and normal assignment/configuration changes for that same audience and revision. A credential reset or approved identity recovery invalidates the old revision. - It is a bearer credential: a copied token can be replayed. It does not by itself prove freshness, current connection, or physical presence. - Keep it out of logs, URLs, analytics, screenshots, and error reports. Send it only to your trusted backend over HTTPS; do not persist it unnecessarily in browser storage. 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: ```http POST /api/kiosk/identity/verify Authorization: apikey Content-Type: application/json { "device_id": "", "auth_key": "", "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: ```json { "status": true, "data": { "ok": true, "device_id": "", "sn": "", "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: ```js 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: ```js 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.`, 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.