Reference

Security

Origin checks, proof verification, and lifecycle constraints.

The plugin keeps the lifecycle narrow: short TTLs, separate tokens per device role, trusted origins, and adapter-owned proof verification. Treat the server plugin as the order coordinator, not as the proof authority.

Trusted origins

trustedOrigins controls which origins can start orders. Origins are normalized without trailing slashes.

auth.ts
crossDevice({
  trustedOrigins: ["https://console.example.com"],
  adapters: [adapter],
})

If trustedOrigins is omitted, the plugin still reads the request origin. For production apps, configure explicit origins.

Token separation

Each token grants one role:

TokenRoleCapability
claimTokenApproving deviceClaim the order once and receive a challenge token.
challengeTokenApproving deviceRead the challenge, approve, or reject.
desktopTokenInitiating deviceCancel or finalize.
eventTokenInitiating deviceSubscribe to status events.

Do not move desktopToken into the claim URL. The approving device should not be able to finalize the initiating device session.

TTLs

Orders expire after orderTtlSeconds, which defaults to 120.

auth.ts
crossDevice({
  orderTtlSeconds: 90,
  adapters: [adapter],
})

Use short TTLs for login and signing flows. Increase the value only when the approving-device ceremony needs more time.

Proof failures

Throw CrossDeviceProofError from an adapter when the proof is invalid but expected.

adapter.ts
throw new CrossDeviceProofError("Signature mismatch", {
  code: "UNAUTHORIZED",
  metadata: { adapterId: "wallet" },
})

Unexpected errors become internal proof failures and are logged server-side.

Origin enforcement

enforceOrigin defaults to true. Keep it enabled unless the adapter deliberately verifies origin inside the proof.

auth.ts
crossDevice({
  enforceOrigin: true,
  adapters: [adapter],
})

When origin enforcement is enabled, approval compares the request origin against the order origin. This prevents a proof created for one app origin from approving an order started by another origin.

Login resolution

resolveLogin is the boundary between a valid proof and a Better Auth user. Return null when the proof is valid but should not create a session.

auth.ts
resolveLogin: async ({ approvedSubject, approvedIdentity }) => {
  const user = await users.findByExternalSubject(approvedSubject, approvedIdentity)
  return user ?? null
}

For sign and transaction orders, finalization returns the approved proof artifact instead of resolving a login.

Checklist

  • Configure explicit trustedOrigins in production.
  • Keep enforceOrigin enabled unless the adapter verifies origin itself.
  • Keep orderTtlSeconds short.
  • Store desktopToken only on the initiating device.
  • Throw CrossDeviceProofError for expected invalid proofs.
  • Return null from resolveLogin instead of creating a session for an unknown subject.

Next Steps

Copyright © 2026