> ## Documentation Index
> Fetch the complete documentation index at: https://veriqa.app/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Veriqa, BCP 247 and the threat model

> See where Veriqa stands on each of the 17 cross-device mitigations in RFC 10027 (BCP 247), use it as input for the risk assessment the RFC requires before deployment, and find out what you can do about the uncovered ones today.

[RFC 10027](https://www.rfc-editor.org/rfc/rfc10027) is IETF Best Current Practice 247 on the
security of cross-device flows, published in August 2026. It describes the threat model Veriqa works
in: a sign-in starts in a browser on one device and is confirmed on a phone in a messaging app. The
RFC requires you to assess the risks before you deploy a cross-device flow and to choose mitigations
based on that assessment (§2, items 1 and 4, MUST). The table below is input for that assessment: for
each of the 17 practical mitigations in Section 6.1 it shows whether Veriqa has it and where to check, and for the
uncovered ones, what you can do today.

Checked against the text of the RFC and the Veriqa source code on November 5, 2026.

## Summary

Of the 17 mitigations in §6.1, Veriqa covers **7**, partly covers **2** and does not cover **6**;
another **2** are addressed to parties other than the authorization server.

The table below describes each mitigation and the risks it addresses, including how to provide the
mitigations the Veriqa core does not have.

To see whether Veriqa covers a lot or a little, we set out to compare how fully other common sign-in
methods cover these mitigations. But RFC 10027 came out in August 2026, and ratings of other methods
against it barely exist yet, so we rated them ourselves against the same table.
An SMS code covers 2 mitigations and partly covers 2; another 7 are set by the server that sends the code rather than by the method itself. Google's
sign-in with a code on another device (RFC 8628), going by [Google's documentation](https://developers.google.com/identity/protocols/oauth2/limited-input-device), covers 3
and partly covers 2; Google has not published an assessment of its own against BCP 247.

An independent [review of Amazon Cognito](https://hidekazu-konishi.com/entry/browser_and_cross_device_oauth_on_amazon_cognito.html) found settings for 3 of the 17 mitigations and none
for 5; it does not address the other 9.

## Threat model

In RFC terms, Veriqa is the Authorization Server. The browser where the sign-in starts is
the Consumption Device, and the phone with the messaging app is the Authorization Device. The QR code
or the link travels from one device to the other over an unauthenticated channel, and only the user
can verify that channel: "The only mitigation against this unauthenticated channel is the user's judgment" (§1.1).

This is what Cross-Device Consent Phishing (CDCP) exploits. The attacker starts a sign-in to the
application themselves and, under some pretext, passes their QR code to the victim or shows it on a
fake page. The victim confirms the sign-in in their messaging app, and the session opens for the
attacker. The attacker does not get the messaging account — they get signed in to the application.
Veriqa's flows are cross-device authorization flows (§3.1), so the RFC's second family of threats,
Cross-Device Session Phishing (CDSP, §3.2), does not apply to them.

Veriqa's flows match the patterns in §3.1 as follows (the mapping is derived from the RFC's
definitions; the RFC itself does not mention Veriqa):

| Veriqa flow | RFC pattern |
| - | - |
| A QR code or a link into a messaging app, the one-tap email, a confirmation your server creates | User-Transferred Session Data (§3.1.1) — the same class as RFC 8628 |
| A link in an email sent to the address typed on the sign-in page | Backchannel-Transferred Session (§3.1.2) — the CIBA class |
| A code from an email, entered on the sign-in page | User-Transferred Authorization Data (§3.1.3) |

The initiator context in the question, short lifetimes and one-time codes lower the risk, but they do
not protect against a forwarded sign-in. Only a [mandatory passkey](#passkeys-alongside-veriqa)
protects against it. The [place factor](#device-proximity-and-the-place-factor), when the surface is
picked based on the session, only stops browsers outside the office, and only if your application
grants access after the presence check.
[Sign-in from trusted networks](https://veriqa.app/docs/scenarios/trusted-network.md) likewise only stops browsers outside
those networks.

## §6.1 mitigations in Veriqa

Statuses: **covered** — a Veriqa mechanism does what the mitigation describes; **partly** — part of
the mitigation is covered, or only one of the flows; **not covered** — Veriqa does not have it;
**outside the product** — the mitigation is addressed to someone other than the authorization server.
The table primarily assesses the sign-in; a confirmation your server creates is counted where it
complements the sign-in. Mitigation names are as in the RFC.

| § | Mitigation | What Veriqa has | Status | Where to check |
| - | - | - | - | - |
| 6.1.1 | Establish Proximity | None of the RFC's four methods — physical connectivity, wireless, shared network, geolocation. In hop mode Veriqa collects the context of the phone that scanned the QR code as a risk signal, but does not match it against the sign-in device. The place factor works today as a recipe: after signing in, the person scans a QR code on an office surface — a printer, a meeting-room screen, an IP phone — and confirms in the messaging app, and your service picks the surface by the network or workstation of the session. What to do: [passkeys](#passkeys-alongside-veriqa), [the place factor](#device-proximity-and-the-place-factor); direction — [binding levels](#binding-levels) | not covered | [Hop mode](https://veriqa.app/docs/concepts/hop-mode.md#security) |
| 6.1.2 | Short-Lived/Time-Bound QR or User Codes | A sign-in transaction expires after 300 seconds by default, a confirmation your server creates after 60 to 1800 seconds; the one-time hop link has a lifetime of its own | covered | [`TransactionTtlSeconds`](https://veriqa.app/docs/reference/configuration#veriqatransactionengine), [`ttl_seconds`](https://veriqa.app/docs/reference/confirmation-api#3-create-the-confirmation), `HopMode.TokenLifetime` |
| 6.1.3 | One-Time or Limited-Use Codes | A transaction can be confirmed only once, and a finished one cannot be replayed; the hop link and the link handle are single-use | covered | [Hop mode](https://veriqa.app/docs/concepts/hop-mode.md), [link handle](https://veriqa.app/docs/scenarios/channel-linking.md#two-bots-handing-the-user-over-to-your-bot) |
| 6.1.4 | Unique Codes | The transaction identifier is 256 random bits from a cryptographically secure generator. A transaction is bound to the channel account in which it is opened first — by its QR code or by its link — and any other account is refused; tokens for it are issued only once. This implements the MAY to decline a code presented again, using the transaction state instead of a list of used codes. When a hop link is opened again in another browser, Veriqa warns the user | covered | `TransactionId.NewId`, `TransactionService.BindChannelSubjectAsync` in the source code; `HopMode.WarnOnRepeatScan`, `true` by default — [Hop mode](https://veriqa.app/docs/concepts/hop-mode.md#key-differences) |
| 6.1.5 | Content Filtering | Filtering email and messages is the job of email providers and messaging apps | outside the product | — |
| 6.1.6 | Detect and Remediate | The transaction audit trail, flagging of an unusual sign-in context, token revocation, and introspection for clients allowed to use it. There is no chain of "attack detected → tokens issued for this code revoked → resource servers notified"; flagging of an unusual context is off by default and has no sign-in history to compare with yet; there is no "this wasn't me" button and no CAEP. Direction — [anomaly detection](#anomaly-detection) | partly | [Audit](https://veriqa.app/docs/guides/hardening.md#logging-and-audit), [`AnomalyDetection:Enabled`](https://veriqa.app/docs/reference/configuration#veriqainitiatorcontext), [`EnableRevocation`](https://veriqa.app/docs/reference/configuration#server), [introspection](https://veriqa.app/docs/reference/oidc-capabilities.md) |
| 6.1.7 | Trusted Devices | The core has no registry of trusted devices allowed to start a sign-in: any browser can start one. Only a confidential client of your backend can create a confirmation. In the place-factor scenario, you choose and register the display device. Direction — [browser mark](#browser-mark) | not covered | [Get a token](https://veriqa.app/docs/reference/confirmation-api.md#2-get-a-token) |
| 6.1.8 | Trusted Networks | The core has no list of networks a sign-in may start from; `KnownIPNetworks` only sets which proxies are trusted. Today the deployment perimeter is what limits sign-ins. What to do and direction — [trusted network](#trusted-network) | not covered | [Infrastructure](https://veriqa.app/docs/reference/configuration.md#infrastructure) |
| 6.1.9 | Limited Scopes | By configuration: the client's `AllowedScopes`. Every Veriqa flow is cross-device, so the client's scopes are the scopes of these flows. There is no narrowing by risk — by device or place; the RFC allows it but does not require it ("may be further refined") | covered | [Clients](https://veriqa.app/docs/reference/configuration.md#clients) |
| 6.1.10 | Short-Lived Tokens | By configuration: access tokens expire after 3600 seconds, refresh tokens after 1,209,600 seconds (14 days), refresh-token rotation is on | covered | [Server](https://veriqa.app/docs/reference/configuration.md#server), [rotation](https://veriqa.app/docs/guides/hardening.md#refresh-token-rotation) |
| 6.1.11 | Rate Limits | Route limits (starting a sign-in — 60 a minute, creating a confirmation — 60 a minute per client, starting an Email sign-in — 5 a minute) and a limit per channel user — 5 a minute. Sign-in emails are also counted per recipient: 5 an hour per address, and per domain if switched on | covered | [`Veriqa:RateLimit`](https://veriqa.app/docs/reference/configuration#veriqaratelimit), [limits](https://veriqa.app/docs/guides/hardening.md#rate-limiting-and-anti-abuse), [emails per address](https://veriqa.app/docs/guides/channels.md#sending-an-email-again) |
| 6.1.12 | Sender-Constrained Tokens | Tokens are not bound to a sender key: no DPoP and no mTLS. No direction on this page | not covered | [OIDC support](https://veriqa.app/docs/reference/oidc-capabilities.md#not-supported) |
| 6.1.13 | User Education | Educating users is up to your service; the RFC makes this a MAY | outside the product | — |
| 6.1.14 | User Experience | The question in the messaging app names the application, the time of the request, and the initiator's browser, OS and region, and ends with the advice `If it was not you, decline the request.` Below it are the "Confirm ✅" and "Decline ❌" buttons, with a red cross marking the refusal. On the Veriqa page that asks **Yes** or **No** and on the provider-account sign-in page, the default answer is **No**: that button is filled with the primary color and has focus when the page opens, while **Yes** is outlined. On a touch screen the provider page accepts **Yes** only via a slider. In a confirmation your server creates, you write the question, and the advice to decline is there if you added it | covered | [Initiator context](https://veriqa.app/docs/concepts/initiator-context.md), [`Veriqa:LoginConfirmation`](https://veriqa.app/docs/reference/configuration#veriqaloginconfirmation), [`ConfirmSlider`](https://veriqa.app/docs/guides/channels#sign-in-with-a-provider-account), [confirmation templates](https://veriqa.app/docs/reference/confirmation-api.md#the-templates) |
| 6.1.15 | Authenticate then Initiate | For a confirmation your server creates: your backend starts it after the user has signed in, and `expected_identities` ensures that only the account owner can confirm it. A repeated sign-in before a risky operation is also started by a user who has signed in, and the step-up recipe checks `sub` and `auth_time` in the new `id_token`. The mitigation does not apply to the sign-in itself: whoever starts it has not signed in yet. The mitigation assumes a phishing-resistant sign-in first — a [mandatory passkey](#passkeys-alongside-veriqa) gives one | partly | [What you check](https://veriqa.app/docs/scenarios/step-up.md#what-you-check) |
| 6.1.16 | Request Initiation Verification | There is no separate one-time-code step. A sign-in with a code from an email is the flow itself (§3.1.3), not an added check. Direction — [binding levels](#binding-levels) | not covered | [Link, code or both](https://veriqa.app/docs/guides/channels.md#link-code-or-both) |
| 6.1.17 | Request Binding with Out-of-Band Data | None. No direction on this page | not covered | — |

## Limitations and future directions

Future directions have no dates; each one gives its current status, and a planned one links to [the release plan](https://veriqa.app/docs/releases/next.md).

### Device proximity and the place factor

**What is not covered.** Veriqa has none of the proximity methods in §6.1.1: a messaging app gives the
server neither radio nor a shared network with the sign-in browser.

**What you can do today.** Build the place factor from the
[Place and identity](https://veriqa.app/docs/scenarios/place-and-identity.md) scenario: after signing in, the person scans a QR
code on a printer, a meeting-room screen or an IP phone and confirms in the messaging app. This is a
compensating control for the risk assessment, not a method in §6.1.1: it checks that the phone is near
the place surface, not near the sign-in device.

A CDCP attack relies on distance and scale: the RFC describes it as sending codes "to large numbers of
users that are geographically distributed" (§6.1.1). A QR code on a sheet from the office printer or
on a meeting-room panel cannot be delivered remotely or sent to a thousand recipients. Where perimeter
policy forbids radio, FIDO proximity over Bluetooth does not work at all (both devices need BLE,
§6.2.3.3), while a paper or on-screen QR code needs no radio.

The place confirms the person, not the session. If your service picks the surface based on the network or
workstation where the session is open, a session outside the office gets no surface and does not pass
the presence check, provided your application grants access only after that check. If the surface is picked based on who signed in, the scenario only narrows the victims
down to those who are on site: with a forwarded sign-in, the person confirms the place in their own
office, and the session stays with the attacker.

**Direction.** The core itself would print the place QR code through a device adapter, and the
browser would not show it. The place QR code would then never appear on a screen, and neither a remote
desktop nor screen sharing would see it. Status: under discussion.

### Passkeys alongside Veriqa

**What is not covered.** The confirmation in the messaging app is not tied to the browser where the
session opens, so a Veriqa sign-in is not phishing-resistant.

**What you can do today.** The [Passkeys alongside Veriqa](https://veriqa.app/docs/scenarios/passkey-with-veriqa.md) scenario:
the user creates a passkey from a button that arrives in the messaging app after the sign-in, and the
application requires the key before it opens the session. A passkey does not sign a request for
another origin. Signing in with a key from another device requires Bluetooth near the sign-in browser
(§6.2.3.3). That is a proximity method in §6.1.1 and the phishing-resistant sign-in §6.1.15 describes.
The RFC recommends FIDO2/WebAuthn wherever the devices support it (§6.2.3.5, SHOULD).

Only the mandatory mode protects, and only the users who have it on: as long as sign-in without the
key remains available, the attacker will use it. Do not let a session opened through Veriqa create a
key: that session may belong to the attacker. Add further keys only after a signature with an
existing one. Creating the first key remains the weak spot: the link in the button is a bearer link.

**Direction.** A passkey as a step of the [binding levels](#binding-levels).

### Binding levels

**What is not covered.** The confirmation is not cryptographically tied to the browser that started
the sign-in (§6.1.1, §6.1.16).

**What you can do today.** [Passkeys alongside Veriqa](#passkeys-alongside-veriqa).

**Direction.** The binding-levels specification describes a scale of trust between the
browser that started the sign-in and whoever confirms it. Its target is a level at which a
confirmation is accepted only from the same device, or from a device cryptographically bound to it in
advance. The candidate mechanisms are a passkey as an additional step, a channel key, and an email
link accepted only by the browser that started the sign-in. The last one would remove the
cross-device step from the email sign-in altogether (§2, item 2). In a non-normative note, the
specification also mentions number matching — §6.1.16 in substance, with the same limitation the RFC
names: an attacker who guides the victim interactively will ask for the number too. The specification
does not promise a particular mechanism. Status: planned for the release after the next one — see
[the release plan](https://veriqa.app/docs/releases/next.md).

### Anomaly detection

**What is not covered.** There is no chain of "attack detected → tokens issued for this code revoked →
resource servers notified", no "this wasn't me" button and no CAEP (§6.1.6). The
`AnomalyDetection:Enabled` switch exists, but the core does not yet keep a history of confirmed
sign-ins to compare with, so flagging stays inactive even when the switch is on.

**What you can do today.** Watch the [transaction audit trail](https://veriqa.app/docs/guides/hardening.md#logging-and-audit)
and revoke the tokens of a sign-in the person did not start
([`EnableRevocation`](https://veriqa.app/docs/reference/configuration#server)).

**Direction.** Veriqa would remember derived, non-personal signals of confirmed sign-ins — the
country and the device type — and flag a sign-in that does not match them. IP, User-Agent and city
would not be stored, and the feature would be opt-in. This would partly cover the detection part of
§6.1.6; revoking tokens and notifying resource servers would stay with your service. Status: planned
for the next release — see [the release plan](https://veriqa.app/docs/releases/next.md).

### Browser mark

**What is not covered.** Any browser can start a sign-in, and Veriqa does not recognize a browser the
person has signed in with before (§6.1.7).

**What you can do today.** Your application knows its own devices: it can refuse to open a session in
an unfamiliar browser without an extra check, such as a passkey.

**Direction.** A browser mark — a cookie for the Veriqa domain under Data Protection (encrypted and
signed), `HttpOnly`, `Secure`. An unfamiliar browser would not receive the authorization code, and
the session would open in a familiar one: the sign-in would start again there, initiated by a third
party (OpenID Connect Core, §4). A web push would make that one tap. The attacker's browser does not
carry this person's mark, and a fake page cannot see the cookies of the Veriqa domain.

This would partly cover §6.1.7. Trust goes to a browser the person has signed in with before, not to
a device the server has registered. A cookie is not attestation, and the RFC points this out among
the limitations of the mitigation. It does not help on a new computer or at the first sign-in. The mark is
exactly as reliable as the protection of the Data Protection key ring: whoever can read the ring can
forge the mark. Encrypt the ring with a certificate —
[`Veriqa:DataProtection:CertificateBase64` or `:CertificatePath`](https://veriqa.app/docs/reference/configuration#infrastructure).
Status: under discussion.

### Trusted network

**What is not covered.** Veriqa has no list of networks a sign-in may start from (§6.1.8).

**What you can do today.** Follow the [Sign-in from trusted networks](https://veriqa.app/docs/scenarios/trusted-network.md)
scenario: on the reverse proxy, restrict `/connect/authorize`, the route that starts a sign-in, to
your networks, and keep the routes the phone and the channel webhooks reach open.

**Direction.** The client entry would hold a list of networks a sign-in may start from. The RFC
advises combining such a control with others (§6.1.8, SHOULD). Status: under discussion.

### Service address

**What is not covered.** There is no check for an unfamiliar browser at the first sign-in.

**Direction.** The messaging app would ask the person to type a Veriqa service address by hand, and
Veriqa would match the browser against the one that started the sign-in. The check would work at the
first sign-in too, but it would rest on the person: a fake page can lead them to its own service page.
It does not literally cover any §6.1 mitigation — it is a hardening step. Status: under discussion.

### A chain of several channels

The channel-chain specification describes a sign-in confirmed in several channels in a row. It protects
against the takeover of one channel account: one stolen messaging session is not enough for the
attacker. It does not protect against a forwarded sign-in — every link of the chain can be forwarded
the same way — and changes no row of the table. You can build it today from the
[Stronger sign-in with a chain of channels](https://veriqa.app/docs/scenarios/auth-chain.md) scenario. Status of built-in
support: frozen.

## Protocol selection (§6.2)

§6.2 compares three protocols: RFC 8628, CIBA and FIDO2/WebAuthn. A Veriqa sign-in uses none of
them: the application gets its tokens through the authorization code flow with PKCE (OpenID Connect),
and the cross-device step runs between the Veriqa page and the messaging app. RFC 8628 and CIBA
describe how a device gets a token; Veriqa answers a different question — how the person proves it is
them.

The RFC recommends FIDO2/WebAuthn wherever the devices support it (§6.2.3.5, SHOULD) and allows it
together with the authorization code flow and PKCE (MAY) — that is how the
[Passkeys alongside Veriqa](https://veriqa.app/docs/scenarios/passkey-with-veriqa.md) scenario works. Where Bluetooth is
forbidden or missing, signing in to a computer with a key on a phone does not work (§6.2.3.3), while
a sign-in through a messaging app does.

## Next steps

> [card] [Preparing for production](https://veriqa.app/docs/guides/hardening.md)
>
> Limits, audit, certificates and token rotation before going live.

> [card] [Initiator context](https://veriqa.app/docs/concepts/initiator-context.md)
>
> What the person sees in the sign-in question.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.