Security
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.
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:
| Token | Role | Capability |
|---|---|---|
claimToken | Approving device | Claim the order once and receive a challenge token. |
challengeToken | Approving device | Read the challenge, approve, or reject. |
desktopToken | Initiating device | Cancel or finalize. |
eventToken | Initiating device | Subscribe 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.
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.
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.
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.
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
trustedOriginsin production. - Keep
enforceOriginenabled unless the adapter verifies origin itself. - Keep
orderTtlSecondsshort. - Store
desktopTokenonly on the initiating device. - Throw
CrossDeviceProofErrorfor expected invalid proofs. - Return
nullfromresolveLogininstead of creating a session for an unknown subject.
Next Steps
- Cross-device flow: review states, tokens, and terminal events
- Endpoint reference: inspect endpoint-level behavior