Skip to main content

Authenticating a user

caution

This tutorial only applies to the Cloud version: you can't register new users in the free version of iCure.

When using your solution, your users will need to be authenticated to iCure in order to access their data. Therefore, you will need to integrate iCure's user authentication process into your product.

When starting your app, the users may be in different situations:

  • They start it for the first time and need to register
  • They already registered and need to log in
  • Their latest login session is still valid, and you can reuse the corresponding authentication token

At the end of this guide, you will be able to implement authentication for those 3 use cases using the iCure MedTech SDK.

Pre-requisites

Make sure to have the following elements in your possession:

  • The iCure reCAPTCHA v3 SiteKey
  • Your msgGtwSpecId
  • Your patientAuthProcessByEmailId and/or patientAuthProcessBySmsId identifiers to authenticate your patient users
  • Your hcpAuthProcessByEmailId and/or hcpAuthProcessBySmsId identifiers to authenticate your healthcare professionals users
info

Currently, you need to contact us at support@icure.com to get this information. However, you you will be able to retrieve it autonomously from the Cockpit in a future release.

Register a user

Let's say your patient Daenaerys uses your app for the first time. You will ask her to sign up. During this procedure, Daenaerys is not known by iCure system yet. Therefore, you can't use the MedTechApi directly. You will have to create an AnonymousMedTechApi instead.

Init AnonymousMedTechApi

const msgGtwUrl = process.env.ICURE_MSG_GTW_URL
const specId = process.env.SPEC_ID
const authProcessByEmailId = process.env.AUTH_BY_EMAIL_PROCESS_ID
const authProcessBySmsId = process.env.AUTH_BY_SMS_PROCESS_ID
const recaptcha = process.env.RECAPTCHA

const anonymousApi = await new AnonymousMedTechApi.Builder()
.withICureBaseUrl(iCureUrl)
.withCrypto(webcrypto as any)
.withMsgGwUrl(msgGtwUrl)
.withMsgGwSpecId(specId)
.withAuthProcessByEmailId(authProcessByEmailId)
.withAuthProcessBySmsId(authProcessBySmsId)
.withCryptoStrategies(new SimpleMedTechCryptoStrategies([]))
.build()

The AnonymousMedTechBuilder asks you to provide multiple information. You will learn more about them in the Here are some details Instantiation How-To, but for now, here is a quick summary:

ArgumentDescription
iCureUrlPathThe URL to contact the iCure API. By default, https://api.icure.cloud is used
msgGtwUrlThe URL to contact the iCure Message Gateway API. By default, https://msg-gw.icure.cloud is used
msgGtwSpecIdYour iCure Message Gateway Identifier. See next section to know more about it
authProcessByEmailIdIdentifier of the authentication by email process. See next section to know more about it
authProcessBySmsIdIdentifier of the authentication by SMS process. See next section to know more about it
cryptoStrategiesCustomizes cryptographical operations. For now you can use the provided Simple implementation.

=======

You can learn about all the options you have when instantiating the MedTech API and the AnonymousMedTech API in the Instantiation How-To.

Since Daenaerys is a patient, you will have to provide the patientAuthProcessByEmailId as a authProcessByEmailId or patientAuthProcessBySmsId as an authProcessBySmsId.

info

If Daenaerys was a doctor, you would instead provide the hcpAuthProcessByEmailId as authProcessByEmailId or hcpAuthProcessByEmailId as authProcessBySmsId.

info

On node.js or React Native, two extra parameters are required to set the way the SDK will handle the internal storage of keys and additional data. The withStorage method allows you to provide a custom implementation of the Storage interface. This implementation is responsible for storing data in platform specific storage facilities. The withKeyStorage method allows you to provide a custom implementation of the KeyStorage interface. This implementation is responsible for storing cryptographic keys in platform specific secure storage facilities.

You can find more information about this in the AnonymousMedTechApiBuilder documentation.

In the browser, default implementations are used that store data and keys in the browser's local storage.

Starting the authentication process

The registration process of iCure uses an one-time password (OTP) sent by email or sms. Therefore, Daenaerys will need to provide at least an email or mobile phone number to register or login.

You will also have to implement the ReCAPTCHA mechanism and provide us the computed score during the startAuthentication process.

info

Check the official reCAPTCHA v3 documentation for more information. Also, do not forget to contact the iCure team to get our ReCAPTCHA SiteKey that you will need to implement the reCAPTCHA

As an alternative, you can use FriendlyCaptcha. In this case, the recaptchaType property of the startAuthentication method should be "friendly-captcha".

const authProcess = await anonymousApi.authenticationApi.startAuthentication(
recaptcha,
userEmail, // Email address of the user who wants to register
undefined,
'Daenerys',
'Targaryen',
masterHcpId,
)
authProcess
{
"requestId": "76a6b852-8d75-474f-a797-920f38564a5b",
"login": "yrjbm2oit-dt@got.com",
"bypassTokenCheck": false
}

As an output, you receive an AuthenticationProcess object, which you will need for next steps of the procedure.

info

The masterHcpId represents the identifier of the dataOwner that will be responsible of Daenaerys user creation. This masterHcpId is optional for healthcare professionals registration but mandatory for patients.

It's good to know that after their registration, user will share all their future data with this responsible. The user may decide to stop sharing their data with this responsible by using the userApi.stopSharingDataWith service. For more information, go to the How-to: Automatically share data with other data owners.

Getting the validation code (OTP)

The iCure Message Gateway will send the validation code to the user. Since Daenaerys decided
to authenticate by email, she can now check her emails to get this code.

info

In a future version of Cockpit, you will be able to edit the email and SMS templating for the authentication process. For now, these have all a default template.

Once Daenaerys retrieves her validation code, she can come back to your app and continue the process.

Completing the authentication process

To complete Daenaerys registration, you will have to call the authenticationApi.completeAuthentication service, by providing two arguments:

  • The previous AuthenticationProcess
  • The validation code Daenaerys received by email

This method will also generate the public and private key for the user, saving them in the keyStorage of the newly created MedTechAPI.

const authenticationResult = await anonymousApi.authenticationApi.completeAuthentication(
authProcess!,
validationCode,
)

const authenticatedApi = authenticationResult.medTechApi

console.log(`Your new user id: ${authenticationResult.userId}`)
console.log(`Database id where new user was created: ${authenticationResult.groupId}`)
console.log(`Your initialised MedTechAPI: ***\${authenticatedApi}***`)
console.log(`RSA key pairs of your new user: ***\${authenticationResult.keyPairs}***`)
console.log(`Token created to authenticate your new user: ***\${authenticationResult.token}***`)

As a result, you receive :

  • The MedTechApi instance to use for Daenaerys (properly initialised);
  • The userId, identifying Daenaerys user uniquely;
  • The groupId, identifying the database in which Daenaerys was created;
  • The keyPair, the RSA keypair generated for the patient;
  • The token, the time-limited token created for Daenaerys, to authenticate her;

Make sure to save these elements to be able to authenticate Daenaerys again when she'll come back on your app.

// saveSecurely does not exist: Use your own way of storing the following data securely
// One option is to put these elements into the localStorage
saveSecurely(
userEmail,
authenticationResult.token,
authenticationResult.userId,
authenticationResult.groupId,
authenticationResult.keyPairs,
)

Now that her authentication is completed, Daenaerys may manage data with iCure.

const createdDataSample = await authenticatedApi.dataSampleApi.createOrModifyDataSampleFor(
loggedUser.patientId,
new DataSample({
labels: new Set([new CodingReference({ type: 'IC-TEST', code: 'TEST' })]),
content: mapOf({ en: new Content({ stringValue: 'Hello world' }) }),
openingDate: 20220929083400,
comment: 'This is a comment',
}),
)
createdDataSample
{
"id": "2932bd69-5e72-4b8e-bb39-6752d6fb0bcc",
"identifiers": [],
"batchId": "66a3be6c-80ff-4006-87f3-eed8e897007c",
"healthcareElementIds": {},
"canvasesIds": {},
"content": {},
"valueDate": 20231115143045,
"openingDate": 20220929083400,
"index": 0,
"created": 1700058645501,
"modified": 1700058645501,
"author": "*",
"responsible": "*",
"comment": "This is a comment",
"qualifiedLinks": {},
"labels": {},
"systemMetaData": {
"secretForeignKeys": [
"eef8c1ed-729c-47cf-8cb5-13ab5ede8391"
],
"cryptedForeignKeys": {},
"delegations": {},
"encryptionKeys": {},
"securityMetadata": {
"secureDelegations": {},
"keysEquivalences": {}
},
"encryptedSelf": "fp5hQGO06DvCO2UnmfVKamU3sZfIeXn9s17mke3Qp1TjZol4blTxaYOa1FmzBr7WaYvWYpZI2jPW/EhVYULw62ZO7JVu3C8aZwFbRuNcsh4=",
"tags": {}
},
"codes": {}
}

But what do you have to do when the authentication token of Daenaerys expires, and she needs to log in again?

Logging in with existing credentials

Each time you complete the registration or login process, you can save the credentials you receive in a secured place. We symbolised it through the saveSecurely method.

// saveSecurely does not exist: Use your own way of storing the following data securely
// One option is to put these elements into the localStorage
saveSecurely(
userEmail,
authenticationResult.token,
authenticationResult.userId,
authenticationResult.groupId,
authenticationResult.keyPairs,
)

The first thing you have to do is to retrieve Daenaerys credentials and her RSA Keypair

// getBackCredentials does not exist: Use your own way of storing the following data securely
// One option is to get them back from the localStorage
const { login, token, keys } = getBackCredentials()

And then, initialise a MedTechApi, authenticating Daenaerys directly.

const reInstantiatedApi = await new MedTechApi.Builder()
.withICureBaseUrl(iCureUrl)
.withUserName(login)
.withPassword(token)
.withCrypto(webcrypto as any)
.withCryptoStrategies(new SimpleMedTechCryptoStrategies(keys))
.build()

The MedTech API will automatically load the keys for that user from the local storage, but you can also pass them explicitly through the withCryptoStrategies method of the builder.

info

You can learn more about the Crypto Strategies here.

Daenaerys can finally manage her data again.

const foundDataSampleAfterInstantiatingApi = await reInstantiatedApi.dataSampleApi.getDataSample(
createdDataSample.id,
)
foundDataSampleAfterInstantiatingApi
{
"id": "2932bd69-5e72-4b8e-bb39-6752d6fb0bcc",
"identifiers": [],
"batchId": "66a3be6c-80ff-4006-87f3-eed8e897007c",
"healthcareElementIds": {},
"canvasesIds": {},
"content": {},
"valueDate": 20231115143045,
"openingDate": 20220929083400,
"index": 0,
"created": 1700058645501,
"modified": 1700058645501,
"author": "*",
"responsible": "*",
"comment": "This is a comment",
"qualifiedLinks": {},
"labels": {},
"systemMetaData": {
"secretForeignKeys": [
"eef8c1ed-729c-47cf-8cb5-13ab5ede8391"
],
"cryptedForeignKeys": {},
"delegations": {},
"encryptionKeys": {},
"securityMetadata": {
"secureDelegations": {},
"keysEquivalences": {}
},
"encryptedSelf": "fp5hQGO06DvCO2UnmfVKamU3sZfIeXn9s17mke3Qp1TjZol4blTxaYOa1FmzBr7WaYvWYpZI2jPW/EhVYULw62ZO7JVu3C8aZwFbRuNcsh4=",
"tags": {}
},
"codes": {}
}

Regenerate the credentials for a User

Once Daenaerys's token is expired, she will need to authenticate again to iCure by starting the login process. This flow is similar to the one of the registration phase.

As Daenaerys is not authenticated anymore, you have to create a new AnonymousMedTechApi instance.

const anonymousApiForLogin = await new AnonymousMedTechApi.Builder()
.withICureBaseUrl(iCureUrl)
.withCrypto(webcrypto as any)
.withMsgGwUrl(msgGtwUrl)
.withMsgGwSpecId(specId)
.withAuthProcessByEmailId(authProcessByEmailId)
.withAuthProcessBySmsId(authProcessBySmsId)
.withCryptoStrategies(new SimpleMedTechCryptoStrategies([]))
.build()

const authProcessLogin = await anonymousApiForLogin.authenticationApi.startAuthentication(
recaptcha,
userEmail, // The email address used for user registration
)

Daenaerys then receives a new validation code by email.

Since you already created an RSA keypair for her, you just need to retrieve it from where you stored it previously and provide it to the completeAuthentication method.

const loginResult = await anonymousApiForLogin.authenticationApi.completeAuthentication(
authProcessLogin!,
validationCodeForLogin,
)

console.log(`Your new user id: ${loginResult.userId}`)
console.log(`Database id where new user was created: ${loginResult.groupId}`)
console.log(`Your new initialised MedTechAPI: ***\${loginResult.medTechApi}***`)
console.log(`RSA key pairs of your user stays the same: ***\${loginResult.keyPairs}***`)
console.log(`The token of your user will change: ***\${loginResult.token}***`)

Do not forget to save these new credentials :

// saveSecurely does not exist: Use your own way of storing the following data securely
// One option is to put these elements into the localStorage
saveSecurely(
userEmail,
authenticationResult.token,
authenticationResult.userId,
authenticationResult.groupId,
authenticationResult.keyPairs,
)
danger

If you are building a web app and store the private key only in your user's browser local storage, you should consider that if the user deletes their browser data, they will lose access to the data they created in iCure. After completing their registration, it might be a good idea to ask your user to store their private key in a safe place in their filesystem, possibly encrypting it with a password.

Make sure your users understand they should never share this file with anyone.

For more information check the In-Depth Explanation What happens if my user loses his private key ?

And Daenaerys may manage her data again :

const loggedUserApi = loginResult.medTechApi

const foundDataSampleAfterLogin = await loggedUserApi.dataSampleApi.getDataSample(
createdDataSample.id,
)
foundDataSampleAfterLogin
{
"id": "2932bd69-5e72-4b8e-bb39-6752d6fb0bcc",
"identifiers": [],
"batchId": "66a3be6c-80ff-4006-87f3-eed8e897007c",
"healthcareElementIds": {},
"canvasesIds": {},
"content": {},
"valueDate": 20231115143045,
"openingDate": 20220929083400,
"index": 0,
"created": 1700058645501,
"modified": 1700058645501,
"author": "*",
"responsible": "*",
"comment": "This is a comment",
"qualifiedLinks": {},
"labels": {},
"systemMetaData": {
"secretForeignKeys": [
"eef8c1ed-729c-47cf-8cb5-13ab5ede8391"
],
"cryptedForeignKeys": {},
"delegations": {},
"encryptionKeys": {},
"securityMetadata": {
"secureDelegations": {},
"keysEquivalences": {}
},
"encryptedSelf": "fp5hQGO06DvCO2UnmfVKamU3sZfIeXn9s17mke3Qp1TjZol4blTxaYOa1FmzBr7WaYvWYpZI2jPW/EhVYULw62ZO7JVu3C8aZwFbRuNcsh4=",
"tags": {}
},
"codes": {}
}

What's next?

Some specific use cases can bring you some questions: what happens if Daenaerys lost her RSA Keypair? What happens if Daenaerys would like to start your app on another device?

All those questions are answered in the children pages of this tutorial.