Pryv.io Multi-Factor Authentication configuration
This document describes how to configure Multi-Factor Authentication (MFA) for the Pryv.io auth.login API method.
Since v2 (2026) MFA is built into the core binary (merged from the standalone
service-mfaprocess). There is no separate MFA container, noplatform.yml, no admin-panel tab — the configuration lives underservices.mfa.*inoverride-config.yml, applied on core restart. MFA is disabled by default; setservices.mfa.modetosingleorchallenge-verifyto enable it.
The prerequisite for this is to have:
- a running Pryv.io v2+ instance
- an external communication service to send messages over another channel, such as email or SMS.
Depending on your communication service capabilities, you will either use the single or the challenge-verify mode.
Table of contents
Flow
You will need to define a template for the API call(s) that will be made to your communication service. The user-specific values that will be substituted in the template will be stored in the user’s private profile.
Setup
MFA must be activated per user account. You can implement this in your onboarding flow or at a later time.
After obtaining a personal token from an auth.login API call, you must call the activate MFA API method, providing the user’s MFA data. This will trigger the challenge sent to the user.
You should confirm MFA activation by sending the obtained challenge in the payload which will be substituted in the related template. If confirmation is successful, the MFA data provided at activation is saved in the user’s private profile, alongside recoveryCodes which you receive for later deactivation.
Usage
Once MFA has been activated for an account, you will receive a mfaToken each time you perform a Login user API call. You will use it to Trigger the MFA challenge where data saved in the private profile will be sent to your communication service.
You will send the received challenge the same way you did for confirmation, but this time using the verify MFA challenge route.
Deactivation and recovery
You may deactivate MFA using a personal token on the deactivate MFA API method. If you have lost access to your 2nd factor such as phone or email, you can also use the recover MFA route to deactivate it using one of the recovery codes.
Modes
The single mode is meant when your communication service only supports sending messages. If it supports creating a challenge and verifying it, you can also use challenge-verify.
In single mode, Pryv.io generates a secret code, sends it to your communication service upon activation and challenge, then verifies it itself during confirmation and verification.
In challenge-verify mode, Pryv.io makes an HTTP request to your communication service to generate and send a code then forwards it during verification.
The templates are set in override-config.yml under services.mfa.sms.endpoints.*.
Configuration
Enabling MFA
Pick a mode — single or challenge-verify — and fill in the matching endpoint:
services:
mfa:
mode: single # or: challenge-verify | disabled
sms:
endpoints:
# Define only the endpoints matching your chosen mode.
# Leave the others as empty strings to keep config-validation happy.
single:
url: ''
method: POST
body: ''
headers: {}
challenge:
url: ''
method: POST
body: ''
headers: {}
verify:
url: ''
method: POST
body: ''
headers: {}
sessions:
ttlSeconds: 1800
With mode: disabled (the default) the MFA API methods return a “not enabled” error — backwards-compatible for deployments that don’t need two-factor authentication.
Endpoint shape
Each endpoint describes the HTTP request Pryv.io makes to your communication service:
url: 'https://api.smsapi.com/mfa/codes?language={{ language }}'
method: 'POST'
body: '{"phone":"{{ phone }}"}'
headers:
authorization: 'Bearer: YOUR-COMMUNICATION-SERVER-API-KEY'
'content-type': 'application/json'
User data
When activating MFA for a user account, variables provided in the request body at activation will be saved in the user’s account. They look like this:
{
"language": "en",
"phone": "41791231212"
}
Parameters
url
You can provide the URL, with the query parameters here as a string. Variables are substituted in the string.
method
The HTTP method, currently supports HTTP POST and GET methods.
body
The request body that will be sent, provided as a string. Variables are substituted in the string.
headers
The request headers that will be sent in the HTTP request. Variables are substituted in the values of these headers.
As the request body is a string, you will have to provide the corresponding content-type header.
Session TTL
services.mfa.sessions.ttlSeconds controls how long a pending challenge stays valid before expiring (default: 1800 seconds / 30 minutes). Sessions are kept in-process (per-core, in-memory) and do not survive a core restart — users in the middle of an MFA challenge at restart time will need to re-trigger.
Single
For single mode, you can provide a {{ code }} variable which will be substituted with a code generated by Pryv.io.
The example hereafter stores the message in the user-specific data, where {{ code }} substitution also works.
Single template
The configuration for single mode describes the HTTP request made by Pryv.io during activation and challenge:
services:
mfa:
mode: single
sms:
endpoints:
single:
url: 'https://api.smsmode.com/http/1.6/sendSMS.do?accessToken=your-api-key&message={{ message }}&emetteur=Pryv%20Lab&numero={{ number }}'
method: 'GET'
Single user data
with the following user data sent during activation:
{
"number": "41791231212",
"message": "Your%20Pryv%20Lab%20MFA%20code%20is%3A%20{{ code }}"
}
Note that the message Your Pryv Lab MFA code is: {{ code }} has been URL-encoded as it will appear in query parameters, but the {{ code }} variable is kept as-is since it must be substituted by Pryv.io.
and confirmation / verification:
{
"code": "12345"
}
Challenge-Verify mode
Challenge-Verify template
The configuration for challenge-verify mode describes two HTTP requests — challenge is made during activation and trigger; verify is made during confirmation and verification:
services:
mfa:
mode: challenge-verify
sms:
endpoints:
challenge:
url: 'https://api.smsapi.com/mfa/codes'
method: 'POST'
body: '{"phone_number":"{{ number }}"}'
headers:
authorization: 'Bearer: your-api-key'
'content-type': 'application/json'
verify:
url: 'https://api.smsapi.com/mfa/codes/verifications'
method: 'POST'
body: '{"phone_number":"{{ number }}","code":"{{ code }}"}'
headers:
authorization: 'Bearer: your-api-key'
'content-type': 'application/json'
Challenge-Verify user data
with the following user data sent during activation:
{
"number": "41791231212"
}
and confirmation / verification:
{
"code": "12345"
}
References
The aforementioned examples use working templates and user data for: