Authenticating a user
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/orpatientAuthProcessBySmsId
identifiers to authenticate your patient users - Your
hcpAuthProcessByEmailId
and/orhcpAuthProcessBySmsId
identifiers to authenticate your practitioners users
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 AnonymousEHRLiteApi.Builder()
.withICureBaseUrl(iCureUrl)
.withCrypto(webcrypto as any)
.withMsgGwUrl(msgGtwUrl)
.withMsgGwSpecId(specId)
.withAuthProcessByEmailId(authProcessByEmailId)
.withAuthProcessBySmsId(authProcessBySmsId)
.withCryptoStrategies(new SimpleEHRLiteCryptoStrategies([]))
.build()
The AnonymousEhrLiteBuilder 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:
Argument | Description |
---|---|
iCureUrlPath | The URL to contact the iCure API. By default, https://api.icure.cloud is used |
msgGtwUrl | The URL to contact the iCure Message Gateway API. By default, https://msg-gw.icure.cloud is used |
msgGtwSpecId | Your iCure Message Gateway Identifier. See next section to know more about it |
authProcessByEmailId | Identifier of the authentication by email process. See next section to know more about it |
authProcessBySmsId | Identifier of the authentication by SMS process. See next section to know more about it |
cryptoStrategies | Customizes 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.
If Daenaerys was a doctor, you would instead provide the hcpAuthProcessByEmailId
as
authProcessByEmailId or hcpAuthProcessByEmailId
as authProcessBySmsId.
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.
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": "e0efdb43-4604-46cd-9372-5cbd0c1d3c5e",
"login": "yt7cvbugm-dt@got.com",
"bypassTokenCheck": false
}
As an output, you receive an AuthenticationProcess
object, which you will need for next steps of the procedure.
The masterHcpId
represents the identifier of the dataOwner that will be responsible of Daenaerys user creation.
This masterHcpId
is optional for practitioners 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.
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.api
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 createdObservation = await authenticatedApi.observationApi.createOrModifyFor(
loggedUser.patientId,
new Observation({
tags: new Set([new CodingReference({ type: 'IC-TEST', code: 'TEST' })]),
localContent: mapOf({ en: new LocalComponent({ stringValue: 'Hello world' }) }),
openingDate: 20220929083400,
}),
)
createdDataSample
{
"id": "3a0aa8d8-de30-405f-9489-dacdd1149467",
"identifiers": [],
"batchId": "6040c8d4-b064-4bdd-b15c-1df0f19a9ba0",
"healthcareElementIds": [],
"index": 0,
"valueDate": 20231115142958,
"openingDate": 20220929083400,
"created": 1700058598090,
"modified": 1700058598090,
"author": "*",
"performer": "*",
"localContent": {},
"qualifiedLinks": {},
"codes": {},
"tags": {},
"systemMetaData": {
"secretForeignKeys": [
"641fd914-ddc6-4b60-bb65-5b1dda0560cb"
],
"cryptedForeignKeys": {},
"delegations": {},
"encryptionKeys": {},
"securityMetadata": {
"secureDelegations": {},
"keysEquivalences": {}
},
"encryptedSelf": "1ybmomsFVFQ7soxxwze5gWk0+mOD6cAFz7iHI7cexFDqPuFT9fKKVZIQ1J+Q9OM1CAZubD7bnYTbYPR0jLJHvRPZXtZjSkNm4pDAHcEH/eM=",
"tags": {}
},
"notes": []
}
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 EHRLiteApi.Builder()
.withICureBaseUrl(iCureUrl)
.withUserName(login)
.withPassword(token)
.withCrypto(webcrypto as any)
.withCryptoStrategies(new SimpleEHRLiteCryptoStrategies(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.
You can learn more about the Crypto Strategies here.
Daenaerys can finally manage her data again.
const foundDataSampleAfterInstantiatingApi = await reInstantiatedApi.observationApi.get(
createdObservation.id,
)
foundDataSampleAfterInstantiatingApi
{
"id": "3a0aa8d8-de30-405f-9489-dacdd1149467",
"identifiers": [],
"batchId": "6040c8d4-b064-4bdd-b15c-1df0f19a9ba0",
"healthcareElementIds": [],
"index": 0,
"valueDate": 20231115142958,
"openingDate": 20220929083400,
"created": 1700058598090,
"modified": 1700058598090,
"author": "*",
"performer": "*",
"localContent": {},
"qualifiedLinks": {},
"codes": {},
"tags": {},
"systemMetaData": {
"secretForeignKeys": [
"641fd914-ddc6-4b60-bb65-5b1dda0560cb"
],
"cryptedForeignKeys": {},
"delegations": {},
"encryptionKeys": {},
"securityMetadata": {
"secureDelegations": {},
"keysEquivalences": {}
},
"encryptedSelf": "1ybmomsFVFQ7soxxwze5gWk0+mOD6cAFz7iHI7cexFDqPuFT9fKKVZIQ1J+Q9OM1CAZubD7bnYTbYPR0jLJHvRPZXtZjSkNm4pDAHcEH/eM=",
"tags": {}
},
"notes": []
}
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 AnonymousEHRLiteApi.Builder()
.withICureBaseUrl(iCureUrl)
.withCrypto(webcrypto as any)
.withMsgGwUrl(msgGtwUrl)
.withMsgGwSpecId(specId)
.withAuthProcessByEmailId(authProcessByEmailId)
.withAuthProcessBySmsId(authProcessBySmsId)
.withCryptoStrategies(new SimpleEHRLiteCryptoStrategies([]))
.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,
)
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.api
const foundDataSampleAfterLogin = await loggedUserApi.observationApi.get(createdObservation.id)
foundDataSampleAfterLogin
{
"id": "3a0aa8d8-de30-405f-9489-dacdd1149467",
"identifiers": [],
"batchId": "6040c8d4-b064-4bdd-b15c-1df0f19a9ba0",
"healthcareElementIds": [],
"index": 0,
"valueDate": 20231115142958,
"openingDate": 20220929083400,
"created": 1700058598090,
"modified": 1700058598090,
"author": "*",
"performer": "*",
"localContent": {},
"qualifiedLinks": {},
"codes": {},
"tags": {},
"systemMetaData": {
"secretForeignKeys": [
"641fd914-ddc6-4b60-bb65-5b1dda0560cb"
],
"cryptedForeignKeys": {},
"delegations": {},
"encryptionKeys": {},
"securityMetadata": {
"secureDelegations": {},
"keysEquivalences": {}
},
"encryptedSelf": "1ybmomsFVFQ7soxxwze5gWk0+mOD6cAFz7iHI7cexFDqPuFT9fKKVZIQ1J+Q9OM1CAZubD7bnYTbYPR0jLJHvRPZXtZjSkNm4pDAHcEH/eM=",
"tags": {}
},
"notes": []
}
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.