Device Flow (RFC 8628)

Enable input-constrained devices to authenticate by displaying a short code that the user enters on a secondary device.

Setup

import { device } from "@robelest/convex-auth/providers";

createAuth(components.auth, {
  providers: [device({ verificationUri: "https://myapp.com/device" })],
});

How it works

  1. Device calls signIn("device") -> gets userCode and deviceCode
  2. Device displays: “Go to myapp.com/device, enter code: WDJB-MJHT”
  3. User visits URL on their phone/laptop, signs in, enters the code
  4. Device polls until authorized -> receives session tokens

Device side (CLI)

Use @robelest/convex-auth/client here because CLIs and device flows usually run outside the browser. The browser entrypoint is for web apps and adds browser-only defaults like passkeys, storage, and URL integration.

import { client } from "@robelest/convex-auth/client";
import { api } from "../convex/_generated/api";

const auth = client({ convex, api: api.auth });

const result = await auth.signIn("device");
if (result.kind !== "deviceCode") {
  throw new Error("Device flow did not start.");
}

const { deviceCode } = result;

console.log(`Go to: ${deviceCode.verificationUri}`);
console.log(`Enter code: ${deviceCode.userCode}`);

await auth.device.poll({ code: deviceCode });
// User is now signed in

Verification page

Build a page at your verificationUri where authenticated users enter the code:

function DeviceVerification() {
  const [userCode, setUserCode] = useState("");

  const handleVerify = async () => {
    await auth.device.verify({ code: userCode });
  };

  return (
    <div>
      <h1>Authorize Device</h1>
      <input value={userCode} onChange={(e) => setUserCode(e.target.value)} />
      <button onClick={handleVerify}>Authorize</button>
    </div>
  );
}

Configuration

OptionDefaultDescription
verificationUriSITE_URL + "/device"URL of your verification page
charset"BCDFGHJKLMNPQRSTVWXZ"User code characters (no vowels, per RFC 8628)
userCodeLength8Code length (displayed as XXXX-XXXX)
expiresIn900 (15 min)Code lifetime in seconds
interval5Minimum polling interval in seconds

Error codes

CodeMeaning
DEVICE_AUTHORIZATION_PENDINGUser hasn’t entered the code yet
DEVICE_SLOW_DOWNPolling too fast
DEVICE_CODE_EXPIREDCode expired, restart the flow
DEVICE_CODE_INVALIDCode not found or already used