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-mfa process). There is no separate MFA container, no platform.yml, no admin-panel tab — the configuration lives under services.mfa.* in override-config.yml, applied on core restart. MFA is disabled by default; set services.mfa.mode to single or challenge-verify to enable it.

The prerequisite for this is to have:

Depending on your communication service capabilities, you will either use the single or the challenge-verify mode.

Table of contents

  1. Flow
    1. Setup
    2. Usage
    3. Deactivation and recovery
  2. Modes
  3. Configuration
    1. Enabling MFA
    2. Endpoint shape
    3. User data
    4. Parameters
      1. url
      2. method
      3. body
      4. headers
    5. Session TTL
  4. Single
    1. Single template
    2. Single user data
  5. Challenge-Verify mode
    1. Challenge-Verify template
    2. Challenge-Verify user data
  6. References

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: