Skip to main content

Set up and use 2FA

The Cardinal SDK already includes all the functionalities to set up and use two-factor authentication for the user. A user of your platform can be asked to provide their 2FA token for all the operations that require an elevated security context (🚧).

Set up 2FA​

To enable 2FA for a user, you need to specify the length of the OTP that the backend should expect, the key used to generate and verify the OTP, and a valid OTP generated with the provided configuration at the current time. The OTP is used by the backend to verify that the provided key is correct before enabling the 2FA. The key should be encoded as a Base32 string.

By default, the OTPs are generated and verified using a Sha1-based HMAC algorithm, as many authenticator apps still do not support other algorithms. You can choose a different algorithm (Sha256 or Sha512) through the optional algorithm parameter of Enable2faRequest.

note

Enabling the 2FA for a user is a security-critical operation that requires the permission to edit the user and an elevated security context.

import com.icure.cardinal.sdk.model.security.Enable2faRequest
import com.icure.kotp.Totp

// val userId = The id of the user
val otpLength = 8
// val otpSecret = A Base32-encoded HMAC key
// val currentOtp = An OTP generated with otpSecret at the current time, e.g. using a TOTP library
sdk.user.enable2faForUser(
userId,
Enable2faRequest(
secret = otpSecret,
otpLength = otpLength,
otp = currentOtp
)
)

You can check whether a user has 2FA enabled for password login through the read-only uses2fa field of the user's systemMetadata.

Use 2FA with the Smart Authentication Manager​

The Cardinal SDK will ask you for the 2FA OTP whenever you try to execute an operation that is critical for security. You can define how your applications asks the OTP by defining a SecretProvider and passing to the SDK when you instantiate it.

Below you will find an example of a basic SecretProvider that asks for the 2FA token. To learn more about the SecretProvider, check this how to

note

This functionality is not available on Python.

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

fun askOneTimeCode(): String {
// This function will ask the user to enter their OTP
return "00000000"
}

val authSecretProvider = object : AuthSecretProvider {
override suspend fun getSecret(
acceptedSecrets: Set<AuthenticationClass>,
previousAttempts: List<AuthSecretDetails>,
authProcessApi: AuthenticationProcessApi
): AuthSecretDetails {
if(acceptedSecrets.contains(AuthenticationClass.TwoFactorAuthentication)) {
val otp = askOneTimeCode()
return AuthSecretDetails.TwoFactorAuthTokenDetails(otp)
} else {
throw IllegalStateException("2FA cannot be used for this operation")
}
}
}

The getSecret method of the AuthSecretProvider will be called internally by the SDK whenever there is the need to ask the user for a more secure token. This implementation first checks if the 2FA can be used to fulfill the request, then, it asks the OTP of the 2FA to the user and returns it.

The AuthSecretProvider can be used to instantiate the Cardinal SDK:

import com.icure.cardinal.sdk.options.AuthenticationMethod
import com.icure.cardinal.sdk.storage.impl.FileStorageFacade
import com.icure.cardinal.sdk.CardinalSdk

val sdk = CardinalSdk.initialize(
projectId = applicationId,
baseUrl = cardinalUrl,
authenticationMethod = AuthenticationMethod.UsingSecretProvider(
loginUsername = loginUsername,
initialSecret = AuthenticationMethod.UsingSecretProvider.InitialSecret.Password(loginPassword),
secretProvider = authSecretProvider
),
baseStorage = FileStorageFacade("./scratch/storage")
)
note

Since SDK 2.6.0, initializing the SDK with only a password for a user that has 2FA enabled does not fail anymore: the SDK asks the secret provider for the OTP when it performs its first authenticated request. Since the SDK already performs authenticated requests during the initialization (for example, to retrieve the current data owner), this happens while initialize is still running: your secret provider must be able to prompt the user for the OTP at that point.

Disable 2FA​

The two-factor authentication configuration can be also removed from a user:

// val userId = The id of the user
sdk.user.disable2faForUser(userId)
note

Disabling the 2FA for a user is a security-critical operation that requires the permission to edit the user and an elevated security context.