Skip to main content

Initialize the SDK

Introduction​

The CardinalSdk interface is the access point to all the features of Cardinal.

The initialization of the SDK requires that you provide some mandatory parameters such as the user authentication details, and some optional additional configurations that you can provide through the SdkOptions parameter.

import com.icure.cardinal.sdk.CardinalSdk
import com.icure.cardinal.sdk.auth.UsernamePassword
import com.icure.cardinal.sdk.options.AuthenticationMethod
import com.icure.cardinal.sdk.options.EncryptedFieldsConfiguration
import com.icure.cardinal.sdk.options.SdkOptions
import com.icure.cardinal.sdk.storage.impl.FileStorageFacade

suspend fun initializeMySdk(username: String, password: String) =
CardinalSdk.initialize(
"MyCardinalApp",
"https://api.icure.cloud",
AuthenticationMethod.UsingCredentials(UsernamePassword(username, password)),
FileStorageFacade("/path/to/storage/directory"),
SdkOptions(
encryptedFields = EncryptedFieldsConfiguration(
patient = setOf("notes", "addresses")
)
)
)

The SDK initialization process will automatically log in the user to the Cardinal backend and load or initialize his cryptographic keys.

Backend URL​

You can access the Cardinal backend through multiple urls, which follow different release schedules.

Authentication method​

There are three main authentication methods supported by the Cardinal SDK: using credentials provided to the SDK at initialization time, using an authentication process, or using a custom secret provider.

During initialization, the SDK will use the provided authentication method to login the user and request JSON web tokens which will be used to authenticate the user for the future requests.

info

You don't need to worry about managing the login and JWTs of the user, you only need to provide the authentication method for the api initialization. The SDK will automatically manage the JWT and refresh them as needed.

Authentication with initialization credentials​

You can provide different types of user credentials:

  • The username/email/phone and user-chosen password
  • The username/email/phone and a temporary login token generated by the Cardinal backend
  • A supported third party authentication token

Username + password​

import com.icure.cardinal.sdk.auth.UsernamePassword
import com.icure.cardinal.sdk.options.AuthenticationMethod

val auth = AuthenticationMethod.UsingCredentials(UsernamePassword("username", "password"))

Username + login token​

You can generate login token through the cockpit or through the Cardinal SDK itself (🚧). The lifespan of a login token is configurable, and there may be multiple login tokens associated with a user. Login tokens can be revoked, but any JWTs generated through that login token won't be automatically revoked.

import com.icure.cardinal.sdk.auth.UsernameLongToken
import com.icure.cardinal.sdk.options.AuthenticationMethod

val auth = AuthenticationMethod.UsingCredentials(UsernameLongToken("username", "token"))

Third party authentication​

import com.icure.cardinal.sdk.auth.ThirdPartyAuthentication
import com.icure.cardinal.sdk.auth.ThirdPartyProvider
import com.icure.cardinal.sdk.options.AuthenticationMethod

val auth = AuthenticationMethod.UsingCredentials(ThirdPartyAuthentication("google-token", ThirdPartyProvider.GOOGLE))

Authentication with a process​

note

This authentication method is unavailable on the Cardinal python SDK

You can use authentication processes to authenticate users using one-time tokens sent via email or SMS.

In this case authentication is done in two steps: the first step will generate a one-time token and sends it to the user via email or sms, then once the user provides the received token the second step will actually complete the authentication and initialize the SDK.

To initialize the SDK with an authentication process you need to provide some additional parameters.

Message gateway URL​

The message gateway is the component of Cardinal backend that generates one-time tokens and sends them to the users.

This is a separate component from the main backend, and therefore you also need to provide its URL. Currently, there is only one deployed message gateway which is available at https://msg-gw.icure.cloud

specId and processId​

The specId is unique to your organization and allows the message gateway to connect to the external services linked to your organization, which are used for example to send SMS to the users.

The processId instead links to the type of authentication process and respective configuration. You may have different types of processes, for example, some processes may only be used for the login of existing users via email or phone, but others may also allow to register patient and healthcare party users. The configuration of the process always includes the email/SMS template, but may also include other parameters depending on the type of process.

The type of process you're using also determines whether you have to pass the email or phone number of the user.

You can get the specId and processId from the cockpit. For a quick start, we suggest that you use the demo setup during the cockpit onboarding, but you can also configure your services and custom processes.

Captcha​

To prevent abuse of the messaging system when using the authentication with a process also requires that you provide a captcha. Currently, the Cardinal SDK supports reCAPTCHA v3 and FriendlyCaptcha. You can configure which service to use through the cockpit, but if you're using the demo setup you will need to use friendly captcha. Refer to the official documentation of the chosen captcha provider to learn how to integrate them in your application.

Example​

import com.icure.cardinal.sdk.CardinalSdk
import com.icure.cardinal.sdk.auth.AuthenticationProcessCaptchaType
import com.icure.cardinal.sdk.auth.AuthenticationProcessTelecomType
import com.icure.cardinal.sdk.auth.AuthenticationProcessTemplateParameters
import com.icure.cardinal.sdk.options.EncryptedFieldsConfiguration
import com.icure.cardinal.sdk.options.SdkOptions
import com.icure.cardinal.sdk.storage.impl.FileStorageFacade

suspend fun initializeMySdk(
userEmail: String,
captchaToken: String
): CardinalSdk {
// The authentication with a process uses a different method, with additional parameters.
val authenticationStep = CardinalSdk.initializeWithProcess(
"MyCardinalApp",
"https://api.icure.cloud",
"https://msg-gw.icure.cloud/",
// Retrieve this from the cockpit, constant for your application.
specId,
// Retrieve this from the cockpit, constant for your application.
processId,
AuthenticationProcessTelecomType.Email,
userEmail,
AuthenticationProcessCaptchaType.FriendlyCaptcha,
captchaToken,
FileStorageFacade("/path/to/storage/directory"),
// Process template parameters are additional parameters which
// may be used by the SMS/email template to the user.
// If you are not using them in your template you can omit them.
AuthenticationProcessTemplateParameters(),
SdkOptions(
encryptedFields = EncryptedFieldsConfiguration(
patient = setOf("notes", "addresses")
)
)
)
// The authentication is not yet complete.
// The user will receive a mail with the validation code required to proceed.
val validationCode = askValidationCode()
return authenticationStep.completeAuthentication(validationCode)
}

Authentication with secret provider​

note

This authentication method is unavailable on the Cardinal python SDK

The last authentication method supported by the SDK is based on a secret provider.

A secret provider is essentially a callback that the SDK can use when it needs to get an authentication secret of the user. Implementing a secret provider requires more effort than providing initialization credentials; however, the authentication with a secret provider is much more flexible when the user needs to perform sensitive operations.

In Cardinal SDK some operations such as the creation of a login token are considered sensitive and can only be performed if the user logged in using a certain category of secrets (configurable for each group). By default, if the user logged in using any secret other than long-lived authentication tokens the api will be allowed to perform sensitive operations. This is because long-lived authentication tokens are considered the least secure authentication secret because they're often cached (for example, saved in the browser local storage) to implement mechanisms such as "remember me".

This means that if you initialize the SDK with long-lived token credentials, the SDK instance can't perform any sensitive operation. Using a secret provider, instead, you can perform the initial login using a long-lived token then, if later you need to perform a sensitive operation, the SDK will automatically ask the secret provider for a better secret. Your secret provider implementation, in turn, may prompt the user for his password and then return it to the SDK.

The following code provides an example implementation of a authentication secret provider that uses a long-lived token when possible and as fallback asks the user for its password. If you want more information on authentication with a secret provider refer to the dedicated documentation page

import com.icure.cardinal.sdk.auth.AuthSecretDetails
import com.icure.cardinal.sdk.auth.AuthSecretProvider
import com.icure.cardinal.sdk.auth.AuthenticationProcessApi
import com.icure.cardinal.sdk.model.embed.AuthenticationClass
import com.icure.cardinal.sdk.options.AuthenticationMethod

val auth = AuthenticationMethod.UsingSecretProvider(
loginUsername = username,
secretProvider = object : AuthSecretProvider {
override suspend fun getSecret(
acceptedSecrets: Set<AuthenticationClass>,
previousAttempts: List<AuthSecretDetails>,
authProcessApi: AuthenticationProcessApi
): AuthSecretDetails {
// If we can use a secret that doesn't require user interaction we prioritize using that.
if (AuthenticationClass.LongLivedToken in acceptedSecrets) {
val cachedToken = getCachedToken()
if (cachedToken != null && previousAttempts.none { it.secret == cachedToken }) {
return AuthSecretDetails.LongLivedTokenDetails(cachedToken)
}
}
// If the SDK is performing a sensitive operation and the long lived token is not sufficient
// we will provide the password
if (AuthenticationClass.Password in acceptedSecrets) {
return AuthSecretDetails.PasswordDetails(askUserPassword())
}
// If our group uses the default configuration, the password will always be an accepted secret
// We can throw an exception if for some reason that is not the case.
throw UnsupportedOperationException(
"This secret provider only support password and long lived tokens."
)
}
}
)

Storage facade​

The Cardinal SDK needs access to some persistent storage solution to save the cryptographic keys of the user and some associated metadata. When initializing the SDK you have to provide a StorageFacade which interfaces the SDK with the storage solution you want to use.

Each version of the SDK comes with one or more storage facade implementations, depending on the platform. For example, on the Cardinal Typescript SDK an implementation that uses the local storage (for browser environments), and another that stores the data as files on the filesystem (for node or other desktop js runtimes).

If none of the provided implementations included in the SDK suits your needs, you can always provide a custom implementation.

On the kotlin multiplatform SDK the storage solutions available depend on the actual platform being used.

From the common code you can access a FileStorageFacade which uses the platform file system (not available on js outside of node).

On kotlin/js the SDK also provides a storage implementation which uses the browser local storage.

import com.icure.cardinal.sdk.storage.StorageFacade
import com.icure.cardinal.sdk.storage.impl.FileStorageFacade
import com.icure.cardinal.sdk.storage.impl.LocalStorageStorageFacade

// Note: the file storage initialization on kotlin is suspend
suspend fun createFileStorage(): StorageFacade =
FileStorageFacade("/path/to/storage/directory")

// Local storage facade is available only on kotlin/js
fun createLocalStorage(): StorageFacade =
LocalStorageStorageFacade()

// Custom storage implementation
class MyVolatileStorage : StorageFacade {
private val storedData = mutableMapOf<String, String>()

override suspend fun getItem(key: String): String? {
return storedData[key]
}

override suspend fun setItem(key: String, value: String) {
storedData[key] = value
}

override suspend fun removeItem(key: String) {
storedData.remove(key)
}
}

Optional parameters​

When initializing the SDK you can provide some optional configurations through the SdkOptions parameter.

Not all optional configuration parameters are available on all platforms.

warning

The parameters marked as occasionally required in the following sections are actually required if certain conditions are met, and the SDK initialization will fail if they aren't provided.

Refer to the content of the section to learn what are these conditions.

Encrypted fields configuration​

Encrypting data is always a trade-off between usability and security. The parts of the data that the Cardinal SDK encrypts by default should be a good compromise between usability and security for most applications. However, the default configuration may not be optimal for your application. If you want to change which parts of your user's data is encrypted, you can provide an EncryptedFieldsConfiguration object at the SDK initialization time.

To learn more about the syntax of the encrypted fields configuration and for more tips on how to configure it you can refer to the dedicated documentation page

import com.icure.cardinal.sdk.options.EncryptedFieldsConfiguration
import com.icure.cardinal.sdk.options.SdkOptions

val options = SdkOptions(
encryptedFields = EncryptedFieldsConfiguration(
patient = setOf("notes", "addresses")
)
)

Key storage​

By default, the cryptographic keys of the user are exported to pkcs8/spki, encoded, and then stored using the storage facade provided through the mandatory parameters. However, you can also customize the key storage solution, by providing an implementation of KeyStorageFacade.

Like for the standard StorageFacade the SDK comes with some implementations of KeyStorageFacade but you can also implement your own. The implementations currently provided by the SDK are wrappers around a standard storage facade that use different encoding solutions for the keys (for example, encoding the key as a JsonWebKey then storing it using a StorageFacade).

note

Custom key storage facades aren't yet supported on the python SDK.

import com.icure.cardinal.sdk.storage.impl.FileStorageFacade
import com.icure.cardinal.sdk.storage.impl.JsonAndBase64KeyStorage
import com.icure.cardinal.sdk.storage.impl.JwkKeyStorage

// This is the same encoding method as the default, but you can use a different underlying storage facade,
// which for example, could store the key files at a different location from everything else
suspend fun base64EncodedKeyStorage() =
JsonAndBase64KeyStorage(FileStorageFacade("/path/to/key/storage"))

suspend fun jwkEncodedKeyStorage() =
JwkKeyStorage(FileStorageFacade("/path/to/key/storage"))

Group selector (occasionally required)​

info

if you don't know what groups and applications are in the Cardinal ecosystem you should read the dedicated documentation page (🚧).

If your application uses a multi-group structure, you may have the same person or organization may have multiple users in different groups, potentially with different roles and different cryptographic keys. In some situations, the authentication method provided to the SDK may be valid for the login of multiple users in different groups. This can happen, for example, when the user uses a third party authentication provider to login.

An instance of the Cardinal SDK, however, is bound to a specific instance of user in a group. In cases where the chosen authentication method allows the user to access multiple groups you need to provide a groupSelector which tells the SDK in which group the user should login.

The implementation of this group selector depends on the needs and intended use of your application. For example, assume you're developing an application for GPs and you have a group for each practice. In some cases, the same GP may work in multiple practices, and therefore exist in multiple groups. If your application is going to be installed on laptops and the doctor can use it in both clinics you should have your group selector interact with the user and ask in which group he wants to login to. However, if your application is going to be installed only on desktop computers, you may have a configuration file that specifies the id of the group that the installation should use.

This group selector is used only during the initialization of the SDK. You can also change the group of an initialized SDK at a later moment using the switchGroup method on the SDK instance.

Client-side password hashing​

The Cardinal SDK hashes the password of the user before sending it to the backend (in addition to salting on the backend before storing the passwords). This ensures that the clear password of the user is never available to us.

The client-side hashing is done using the application id as a salt.

If you want to disable this behavior set the saltPasswordWithApplicationId property of the SdkOptions to false.

Cryptography configurations​

You can configure the way the Cardinal SDK does some of its cryptographic operations. For example, you can implement custom key recovery solutions. You can also reduce the trust you put on the Cardinal backend by providing a custom key verification method, to make sure that the users are sharing data with who they intend.

These topics are a bit advanced, but we recommend that you read about them after you've familiarized with the cardinal SDK. The parameters that allow you to configure the cryptographic operations of the Cardinal SDK are:

  • cryptoStrategies: allows you to provide custom key generation, recovery and verification solutions (🚧).
  • createTransferKeys: allows you to disable the automatic creation of information that allows to recover newly created keys using previously existing lost but verified keys of the user (🚧).
  • useHierarchicalDataOwners: enables the hierarchical data owners key management when set to true. The SDK will expect to have keys for the data owners parents (if any).

Http client configuration (kotlin only)​

You can configure the ktor client used by the SDK perform requests to the backend through the httpClient property. If you configure the client you should also provide the configured Json serializer through the httpClientJson property, or there may be some inconsistencies in the behavior of the SDK.

If you don't provide a custom client the SDK uses a client shared across all instances of the SDK. If you need to close this client you can use the CardinalSdk.closeSharedClient method.

Cryptographic primitives service configuration (occasionally required)​

All the cryptographic operations performed by the Cardinal SDK use some cryptographic primitives such as RSA or AES. On each platform, the Cardinal SDK uses the native cryptographic primitives solutions (for example, subtle crypto on js, or SecKey on apple platforms).

However, there are some situations where the SDK can't figure out automatically which cryptographic primitives can be used, and in those cases you need to provide a CryptoService instance through the SdkOptions.

Currently, this is the case only for the typescript SDK when it is used on react-native. Refer to the boilerplate documentation for more information on how you can provide the appropriate crypto service to the SDK (🚧).

Legacy support configurations​

The configuration options that were omitted from this page are used only to support the migration of legacy data.

You will not need to use these configurations unless you've been instructed to do so by our team.