You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Tracked in the compliance matrix as MFA-MODERN-METHODS.
Backlog — MFA: document the extension point + ship reference plugins (TOTP / WebAuthn)
Filed: 2026-05-19 (from the compliance-matrix implementer-perspective
gap-probing session — Q4 on authentication strength). Related: sits in the perspective of the OAuth2 + signed-request +
app-account modernisation work (issue #36). The MFA-methods question is
the second factor's modernisation alongside that work's modernisation of
the first factor + bearer-token model.
What's already shipped
MFA is pluggable. An abstract base class components/business/src/mfa/Service.ts defines challenge() and verify() methods that subclasses override. Two subclasses ship
today:
ChallengeVerifyService — two-step external provider
(one HTTP endpoint sends the challenge, another verifies the
response).
SingleService — one-step external provider (single HTTP
endpoint does both).
Both currently target SMS providers by convention — the mfaConfig shape under services.mfa carries challenge / verify
URL templates with {{ username }} placeholder substitution. But
the abstraction itself is generic over HTTP-callable providers.
An operator can today plug in any HTTP-based MFA provider that
fits the challenge/verify or single-step shape by editing services.mfa config — without writing code. That includes:
An operator writing a new subclass can extend Service to plug in any provider — internal or external.
What's missing
Three real gaps:
1. Documentation
docs/pryv-primitives.md MFA section currently says
"SMS-based by default; opt-in per services.mfa.mode". That
phrasing reads as "Pryv supports SMS-MFA only", which is wrong —
it reads as a feature scope when it's actually a default. Auditors
implementers asking "is your MFA AAL2-compatible?" need to see
the extension point + the supported plugin shapes.
Doc gaps to fill:
Update docs/pryv-primitives.md MFA primitive description to
call out the extension point, not just the SMS default.
Add a "writing an MFA provider" page to dev-site (config-driven
HTTP provider, then code-level Service subclass).
Examples in dev-site for at least: a TOTP service config (using
e.g. Twilio Verify or an internal TOTP server), and an external
push provider config.
A statement of which AAL the shipped subclasses can achieve when
configured with which providers (e.g., Authy push → AAL2; SMS-only
via Twilio → AAL1 effectively under NIST AAL guidance).
2. Reference plugin: TOTP (RFC 6238) server-side
The current Service shape assumes the provider lives outside
Pryv (HTTP roundtrip). A TOTP implementation can be HTTP-based
(point at an external Twilio Verify endpoint), but it's also
common to want server-side TOTP where Pryv stores the secret +
verifies the code without an external dependency.
For server-side TOTP, an operator needs to write a Service
subclass that:
Stores the user's TOTP secret on a system-stream at enrollment.
On verify(), reads the secret + runs RFC 6238 TOTP verification
against the submitted code.
Generates recovery codes at enrollment (single-use, stored
encrypted at rest).
A reference TOTP subclass shipped in components/business/src/mfa/
would let operators enable strong AAL2 MFA without a third-party
service dependency.
WebAuthn does not fit the current Service shape because it's
not a simple HTTP challenge/verify — it requires:
Server-issued CredentialCreationOptions (random challenge +
RP info) at enrollment.
Server verification of AuthenticatorAttestationResponse on enroll
(signature verification against attestation statement).
Server-issued challenge + verification of
AuthenticatorAssertionResponse on each login.
Public-key + signature-counter storage per credential per user.
This is a substantively different abstraction. Two paths:
Path A — broaden the Service base class to handle
non-HTTP-roundtrip ceremonies (challenge/verify methods that
return / accept structured data rather than just calling HTTP
endpoints). Existing SMS subclasses stay.
Path B — add a sibling abstraction LocalService for
ceremonies that happen in-process (WebAuthn, server-side TOTP).
Keep Service for HTTP-provider passthrough.
Either path is roughly the same amount of code. Path B is cleaner
because the two patterns (external HTTP provider vs in-process
ceremony) really are different beasts; merging them into one base
class would force every subclass to handle both cases.
Once the abstraction is in place, a WebAuthnService reference
implementation ships. Storage: WebAuthn credential record per user
(credential ID, public key, signature counter, transports) on the
MFA system-stream.
Constraints
Must coexist with the SMS implementations already shipped.
Operator config services.mfa.allowedMethods (or the operator
picking a single service via services.mfa.providerType)
determines which is active. Multi-method per user is a stretch
goal.
Recovery flow: TOTP + WebAuthn need recovery-code provisioning at
enrollment, stored encrypted at rest, single-use.
MFA test series (MA01-MA09) extends with per-method tests
(MA10+ for TOTP, MA20+ for WebAuthn, etc.).
WebAuthn requires HTTPS in production (already satisfied by Pryv's
default TLS posture).
Relationship to the OAuth2 work
The OAuth2 work (issue #36) modernises the authorization layer (RFC 6749 OAuth2,
app accounts, signed requests, operator-side revocation).
This MFA work is orthogonal but adjacent: it modernises the authentication layer (strong primary + second factor).
That work's side notes already include "3rd party SSO" — both fit the
same modernisation arc. A vanilla RFC 6749 client wants both a real
OAuth2 flow and a real second factor. A modern HIPAA / ISO /
DiGA-compliant deployment needs both.
Sequencing options:
Doc-only fix first (no code change; lifts the matrix posture by
making the extension point visible). Hours of work.
Plus reference TOTP subclass (no abstraction change; days).
Plus WebAuthn (needs the LocalService abstraction; weeks).
Sequencing 1 → 2 → 3 lets each step ship independently while the OAuth2 work
proceeds on its own track.
Status
Not started. Backlog.
Matrix impact when shipped
Scope
Row
Today
After step 1 (docs)
After step 3 (WebAuthn)
hipaa-security
164.312(d)
Impl | High
unchanged; caveat removed
unchanged; tests expanded
iso-27001
A.8.5
Impl | High
unchanged
unchanged; per-method tests
iso-27001
A.5.17
F: Infra | Med
unchanged
F: Infra | High
diga
A1.2.4
Impl | High
unchanged
unchanged
hipaa-security
164.308(a)(5)(ii)(D)
Configurable | Med
Configurable | High
unchanged
The current Implemented | High rows are defensible against regulator
text (HIPAA / ISO / DiGA don't prescribe specific factors). Step 1
(documentation) removes the ambiguity that makes the rows look weak
under auditor scrutiny.
Tracked in the compliance matrix as
MFA-MODERN-METHODS.Backlog — MFA: document the extension point + ship reference plugins (TOTP / WebAuthn)
Filed: 2026-05-19 (from the compliance-matrix implementer-perspective
gap-probing session — Q4 on authentication strength).
Related: sits in the perspective of the OAuth2 + signed-request +
app-account modernisation work (issue #36). The MFA-methods question is
the second factor's modernisation alongside that work's modernisation of
the first factor + bearer-token model.
What's already shipped
MFA is pluggable. An abstract base class
components/business/src/mfa/Service.tsdefineschallenge()andverify()methods that subclasses override. Two subclasses shiptoday:
ChallengeVerifyService— two-step external provider(one HTTP endpoint sends the challenge, another verifies the
response).
SingleService— one-step external provider (single HTTPendpoint does both).
Both currently target SMS providers by convention — the
mfaConfigshape underservices.mfacarries challenge / verifyURL templates with
{{ username }}placeholder substitution. Butthe abstraction itself is generic over HTTP-callable providers.
An operator can today plug in any HTTP-based MFA provider that
fits the challenge/verify or single-step shape by editing
services.mfaconfig — without writing code. That includes:An operator writing a new subclass can extend
Serviceto plug inany provider — internal or external.
What's missing
Three real gaps:
1. Documentation
docs/pryv-primitives.mdMFA section currently says"SMS-based by default; opt-in per
services.mfa.mode". Thatphrasing reads as "Pryv supports SMS-MFA only", which is wrong —
it reads as a feature scope when it's actually a default. Auditors
the extension point + the supported plugin shapes.
Doc gaps to fill:
docs/pryv-primitives.mdMFA primitive description tocall out the extension point, not just the SMS default.
HTTP provider, then code-level
Servicesubclass).e.g. Twilio Verify or an internal TOTP server), and an external
push provider config.
configured with which providers (e.g., Authy push → AAL2; SMS-only
via Twilio → AAL1 effectively under NIST AAL guidance).
2. Reference plugin: TOTP (RFC 6238) server-side
The current
Serviceshape assumes the provider lives outsidePryv (HTTP roundtrip). A TOTP implementation can be HTTP-based
(point at an external Twilio Verify endpoint), but it's also
common to want server-side TOTP where Pryv stores the secret +
verifies the code without an external dependency.
For server-side TOTP, an operator needs to write a
Servicesubclass that:
verify(), reads the secret + runs RFC 6238 TOTP verificationagainst the submitted code.
encrypted at rest).
A reference TOTP subclass shipped in
components/business/src/mfa/would let operators enable strong AAL2 MFA without a third-party
service dependency.
3. Reference plugin / abstraction: WebAuthn (W3C Level 3)
WebAuthn does not fit the current
Serviceshape because it'snot a simple HTTP challenge/verify — it requires:
RP info) at enrollment.
(signature verification against attestation statement).
AuthenticatorAssertionResponse on each login.
This is a substantively different abstraction. Two paths:
Servicebase class to handlenon-HTTP-roundtrip ceremonies (challenge/verify methods that
return / accept structured data rather than just calling HTTP
endpoints). Existing SMS subclasses stay.
LocalServiceforceremonies that happen in-process (WebAuthn, server-side TOTP).
Keep
Servicefor HTTP-provider passthrough.Either path is roughly the same amount of code. Path B is cleaner
because the two patterns (external HTTP provider vs in-process
ceremony) really are different beasts; merging them into one base
class would force every subclass to handle both cases.
Once the abstraction is in place, a
WebAuthnServicereferenceimplementation ships. Storage: WebAuthn credential record per user
(credential ID, public key, signature counter, transports) on the
MFA system-stream.
Constraints
Operator config
services.mfa.allowedMethods(or the operatorpicking a single service via
services.mfa.providerType)determines which is active. Multi-method per user is a stretch
goal.
enrollment, stored encrypted at rest, single-use.
MA01-MA09) extends with per-method tests(MA10+ for TOTP, MA20+ for WebAuthn, etc.).
default TLS posture).
Relationship to the OAuth2 work
The OAuth2 work (issue #36) modernises the authorization layer (RFC 6749 OAuth2,
app accounts, signed requests, operator-side revocation).
This MFA work is orthogonal but adjacent: it modernises the
authentication layer (strong primary + second factor).
That work's side notes already include "3rd party SSO" — both fit the
same modernisation arc. A vanilla RFC 6749 client wants both a real
OAuth2 flow and a real second factor. A modern HIPAA / ISO /
DiGA-compliant deployment needs both.
Sequencing options:
making the extension point visible). Hours of work.
Sequencing 1 → 2 → 3 lets each step ship independently while the OAuth2 work
proceeds on its own track.
Status
Not started. Backlog.
Matrix impact when shipped
The current
Implemented | Highrows are defensible against regulatortext (HIPAA / ISO / DiGA don't prescribe specific factors). Step 1
(documentation) removes the ambiguity that makes the rows look weak
under auditor scrutiny.
Related
compliance-matrix/proposals/mfa-modern-methods.md.components/business/src/mfa/Service.tsChallengeVerifyService.ts+SingleService.ts.