# Protection with 2-man-rule

In the previous step, we had achieved a satisfying version from a security point of view.

Nevertheless, this version still has a user experience drawback: if the user loses their password, they can no longer recover their Seald identity, and thus lose the ability to decrypt their data.

In this step, we will replace the password-protection with the 2-man rule protection method. This method requires a challenge to be sent to the user. Initially, this challenge will be sent by email, then we will see how to send it by SMS.

The branch on which this step is based is 4-signup-jwt (opens new window), the result with the email challenge is 5-two-man-rule (opens new window). The implementation with the SMS challenge is 6-two-man-rule-sms (opens new window).

# Explanation

As explained in the dedicated guide, the purpose of the 2-man-rule protection mode is to give users "the right" to forget their password. The downside is that this mode is not strictly end-to-end.

It works by "splitting" the identity in two, and placing one half - called twoManRuleKey - on the backend subject to authentication, and the other half - called encryptedIdentity - on Seald SDK Key Storage (SSKS) subject to independent email confirmation authentication.

To implement 2-man-rule protection you need to:

  • modify the backend to:
    • store a twoManRuleKey for each user in database ;
    • expose an authenticated API point to generate, re-engage and retrieve the twoManRuleKey, and for the frontend to interact with SSKS to store & retrieve the second half of the encryptedIdentity key;
  • modify the frontend to:
    • replace the password protection plugin with the @seald-io/sdk-plugin-ssks-2mr plugin for 2-man-rule protection;
    • manage the independent authentication step by SSKS.


For faster execution, you can use a rawTwoManRuleKey instead of the twoManRuleKey. For more details, see the "Using a raw TwoManRule Key" paragraph of the identities guide.

# Modifying the backend

We will do the following:

  • modify the User model to be able to store a twoManRuleKey for each user in database;
  • add variables in the configuration file to authenticate the backend connection to SSKS;
  • expose a sendChallenge2MR API point to generate/retrieve the twoManRuleKey and create a session between the frontend and SSKS to drop/retrieve the second half of the encryptedIdentity key.

# Modification of the User model

In the backend/models.js file, we add the twoManRuleKey property to the User model initialization:

# Configuration file

We add two variables to the backend configuration file:

Name Default value Required Description
KEY_STORAGE_URL undefined Yes URL of SSKS, available on the Seald administration panel
KEY_STORAGE_URL_APP_KEY undefined Yes API key ssksAppKey, available on the Seald administration panel

To learn where you can get these, you can check the paragraph about it in the 2-man-rule integration guide.

# API point sendChallenge2MR

This API point does in one request the two things described in the dedicated integration guide:

  • it generates & stores if needed, then returns the twoManRuleKey associated with a user in your application;
  • it uses the SSKS API for the user to authenticate to SSKS.

In the backend/routes/account.js file, we will add an authenticated POST route '/sendChallenge2MR'.

To interact with SSKS we will use node-fetch, so we need to install it:

cd backend
npm install node-fetch

Then import it in the file backend/routes/account.js:

Finally, we create the /sendChallenge2MR route which will:

  • instruct SSKS, if the user authentication is necessary, to send an email in the format of the template to the user's email address that will contain a challenge (valid for 6h) that the user will return to SSKS in the session identified by the twoManRuleSessionId;
  • generate, store & return the twoManRuleKey to the user;
  • inform the frontend if an authentication is necessary to store the identity on SSKS.


It is important to give a valid email address or phone number, as well as a template containing $$CHALLENGE$$ in a visible way, since SSKS will replace $$CHALLENGE$$ with a random challenge that the user will have to copy.

Now that we have made all the necessary changes to the backend, let's move on to the frontend.

# Modification of the frontend

We will do the following:

  • modify the API client frontend/src/services/api.js to include this new API point, and remove the previously added password pre-derivation, which is useless with the 2-man-rule protection mode;
  • modify the Seald service frontend/src/services/seald.js to modify the identity creation and retrieval functions;
  • add, if requested by the backend (if mustAuthenticate is true), the email confirmation by SSKS step in the UI in the SignIn.jsx and SignUp.jsx.

# Client API

In the frontend/src/services/api.js file, add the sendChallenge2MR: body => POST('/account/sendChallenge2MR', body) route to the APIClient, then add a static sendChallenge2MR method to the User class:

Then, we can remove the pre-derivation of the password in the createAccount and login methods.

# Seald service

# Configuring

We start by installing the plugin @seald-io/sdk-plugin-ssks-2mr, remove the plugin @seald-io/sdk-plugin-ssks-password and scyrpt:

cd frontend
npm install @seald-io/sdk-plugin-ssks-2mr

npm uninstall @seald-io/sdk-plugin-ssks-password scrypt

Then, in the file frontend/src/services/seald.js, import it and add it to the SDK. We also import User which we will need later:

# Identity creation and storage

Saving the identity is now done in two steps:

  • the request to send the challenge;
  • storing the identity on SSKS, with or without a challenge depending on the value of mustAuthenticate.

To do this, we modify the createIdentity function which no longer needs the password as an argument, and instead of saving the identity by password, requests a challenge via the API point created above:

This function returns an object containing twoManRuleKey, twoManRuleSessionId (to avoid confusion with the sessionID of the client authentication on the backend), and mustAuthenticate, sent by the backend.

If mustAuthenticate is true, SSKS sends an email. This email or text message will contain a challenge valid for 6h, which will need to be copied by the user.

If mustAuthenticate is false, storing the identity on SSKS can be done without a challenge.

We create a saveIdentity2MR function that will store the identity on SSKS:

Once this function is called, the identity will be saved on SSKS.

# Identity Recovery

When recovering an indentity, authentication with SSKS is mandatory.

The identity recovery is now done in two steps:

  • sending the challenge;
  • using the challenge to retrieve the identity from SSKS.

First we add a sendChallenge2MR function which just makes a simple call to the API point previously created on the backend:

Then, we change function retrieveIdentity to retrieveIdentity2MR which doesn't take password as argument anymore, but takes as new arguemtns:

  • emailAddress: email address of the user;
  • twoManRuleKey: key stored by the backend;
  • twoManRuleSessionId: session id created by the backend for the user at SSKS;
  • challenge: random challenge sent by e-mail.

And we simply change the plugin used to call the sealdSDKInstance.ssks2MR.retrieveIdentity function with these arguments:

After calling this function, the identity will be retrieved and the SDK will be usable.


It is recommended not to retrieve the same identity with ssks2MR.retrieveIdentity on multiple devices at the same time, at the same exact instant, for example during automated tests. Please wait until one of the devices has finished retrieving the identity before starting the retrieval on another device.

# Modification of the user interface

From now on, you have to call these functions:

  • at account creation;
  • at login.

# Account creation SignUp.jsx

We start by adding two status hooks:

Then, we add a ChallengeForm component which is a form allowing the user, when mustAuthenticate is true, to type the challenge and which calls handleChallengeSubmit when the form is validated:

This ChallengeForm will be used instead of the SignupForm if challengeSession is defined:

Next, we modify the end of the handleSignupSubmit so that it displays the ChallengeForm to the user if mustAuthenticate is true, and so that it stores the identity directly otherwise:

Finally, we create handleChallengeSubmit which uses the challenge completed by the user to save his identity on SSKS:

That's it, we have implemented all the changes in the frontend.

# 2-man-rule by SMS

Once the two-man-rule with an email challenge has been implemented, it is very easy to switch to the SMS challenge. We will need to change the frontend to ask the user for a phone number, and then record it in the backend database. We will then make some minor changes to the @seald-io/sdk-plugin-ssks-2mr plugin.

# Modifying the User model in the backend

In the backend/models.js file, we add the phoneNumber property to the User model initialization:

# Modification of the API point and the associated validator

We need to add a phoneNumber field in the query validator:

The phoneNumber field should then be retrieved and passed to the model:

# Modifying the sendChallenge2MR API point

The changes here are easy. The auth_factor needs to be changed. Its type changes from 'EM' to 'SMS', and its value changes to contain the phone number.

Secondly, the template field needs to be changed. This no longer contains the HTML code of the email, but the text of the SMS. Again the template must contain the key $$CHALLENGE$$ which will be replaced by the challenge.

# Fronted modifications

The modifications on the frontend are very similar to those on the backend. You will need to:

  • Add a phone number field to the SignupForm.
  • Add a phoneNumber key in the user model.
  • Modify the account creation API point and call to include the phone number.

Once these three changes have been made, change the saveIdentity2MR and retrieveIdentity2MR functions in the seald.js file. We need to add a phoneNumber argument to these functions, and then change the authFactor argument when calling the plugin functions. Again, the type of the authentication factor becomes 'SMS', and its value becomes the phone number.

# Conclusion

We were able to implement the 2-man-rule protection completely, improvements can still be made, for example instead of asking the user to copy the challenge it is possible to include it in a magic link to the application (be careful not to use a GET variable but a URL fragment so that the server does not know the challenge).