# 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 theencryptedIdentity
key;
- store a
- 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.
- replace the password protection plugin with the
TIP
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 atwoManRuleKey
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 thetwoManRuleKey
and create a session between the frontend and SSKS to drop/retrieve the second half of theencryptedIdentity
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 achallenge
(valid for 6h) that the user will return to SSKS in the session identified by thetwoManRuleSessionId
; - generate, store & return the
twoManRuleKey
to the user; - inform the frontend if an authentication is necessary to store the identity on SSKS.
TIP
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
istrue
), the email confirmation by SSKS step in the UI in theSignIn.jsx
andSignUp.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.
WARNING
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 theSignupForm
. - 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
).