4. User Authentication

Note

  • To use Authentication in a supported web framework please see integrations.

Warning

  • This does not protect you against brute force attacks - make sure to enable rate limiting on your host.

  • Usernames are not encrypted.

  • User classes are not thread-safe. Please create a new object to use in each thread!

  • Krptn does not verify the security of the password (e.g: complexity), please do this yourself!

  • User names cannot be longer then 450 characters

Here is an example usage of creating a new user:

1from krypton.auth.users import userModel
2
3model = userModel.standardUser(None)
4model.saveNewUser("Test_UserName", "Test_Password")

Warning

Please be carefull when setting credentials. The reasons are the following:

  • If you lose your credentials, and have not enabled password reset, you will permanently loose access to your account and data. To enable password reset, please read this document or skip to the part about password resets.

  • The encryption of your data is derieved from your credentials. Therefore, weak password equates to easily cracked encryption.

All that said, don’t panic :-); just enable password resets and validate user passwords for length, complexity, etc…

To retreive the user and set user data as key-value pairs:

1model = userModel.standardUser(userName="Test_UserName")
2sessionKey = model.login(pwd="Test_Password") # See below what sessionKey is
3model.setData("test", "example") # test is the key and example is the value
4data = model.getData("test") # Gives b"example". Would raise ValueError on error.
5model.deleteData("test")

Note

Do make sure that the key in setData does not start with _ - those are reserved for Krypton internals.

You can also use model.encryptWithUserKey with model.decryptWithUserKey, or shareSet with shareGet, if you want other users to read it. Please study the Data Sharing section of this document.

Warning

In setData only the stored values are encrypted. Keys are plaintext!! Avoid storing sensitive data in keys!

4.1. User Sessions

Session keys can be used to restore a session after the user object has been destroyed. For example, in a webserver, the session key could be stored in a cookie, so that the model can be retrieved on each request.

To restore a session:

1model = userModel.standardUser(userName="Test_UserName")
2model.restoreSession(sessionKey)

To set session expiry please see Configuration.

4.2. Sign out of all sessions

1model.revokeSessions()

4.3. Change Username

In case you want to change the user’s username, you can simply do this by calling the changeUserName method.

1model.changeUserName("NewName")

4.4. MFA

To avoid getting locked out, you may want to read Password Reset section of this document.

Before using MFA, make sure that the required configuration values are set.

4.4.1. TOTP

To enable:

1secret, qr = model.enableMFA() 
2# Secret is a shared secret and qr is a string, that when converted to QR code can be scanned by authenticator apps. 
3# If QR Codes are not supported by the app, you can tell the user enter secret instead. 
4# You MUST discard these once the user enabled MFA.

When logging in:

1model.login(pwd="pwd", mfaToken="123456")

If a wrong code is provided, Krptn will impose a 5 second delay to slow brute force attacks. However, please note that is not enough to fully protect you. Therefore, it is necessary to employ a proper rate limiting solution on your webserver.

To disable TOTP (user must be logged in):

1model.disableMFA()

Note

On a failed login attempt, we will impose a 5 second delay to slow down brute force attacks. This is not available for purely password based authentication, so please do impose rate limiting protection on your server.

4.4.2. FIDO Passwordless

See FIDO Docs.

4.5. Data Sharing

Using these methods, you can grant another user access to some of the user’s data.

While deploying these methods, all data remains encrypted using the user’s credentials. No data is ever plaintext in a database! We use Elliptic-curve Diffie–Hellman to share a common encryption key between the users, and we encrypt the data with the common key. Each user has their own Elliptic Curve key, with the private key encrypted with the user’s credentials.

Warning

One thing to note: if the original user used to set/encrypt the data is deleted. All other users will loose access. It is important that the other users create their own copy if they want to retain it.

4.5.1. Sharing

 1model = userModel.standardUser(None)
 2model.saveNewUser("Test_UserName", "Test_Password") # Note: if a user with the same username exists an ValueError will be raised.
 3
 4model2 = userModel.standardUser(None)
 5model2.saveNewUser("Test_UserName2", "Test_Password")
 6
 7# Save value "data" with key "test" and allow access to user "Test_UserName"
 8user2.shareSet("test", "data", ["Test_UserName"])
 9value = model.shareGet("test") # returns b"data". Raises ValueError on error.
10user2.shareDelete("test") # deletes the data - can only be done by the user who shared it

Note

Do make sure that the key in shareSet does not start with _ - those are reserved for Krypton internals.

If the same user has shareSet to another user multiple times with the same name, it is undefined which one will be returned when the recieving user calls shareGet. To avoid such conflicts, make sure to shareDelete data, or use a different name.

As you can see above, shareSet requires you to pass a unique name for the data ("test" in this case), the data ("data" in this case), and a list of usernames who can access it (["Test_UserName"] above).

4.5.2. Encryption

When possible, it is preferred to use shareSet and shareGet but when required you can directly use only Krptn’s encryption capabilities. E.g: if you want to use another database to store this data.

 1model = userModel.standardUser(None)
 2model.saveNewUser("Test_UserName", "Test_Password")
 3
 4model2 = userModel.standardUser(None)
 5model2.saveNewUser("Test_UserName2", "Test_Password")
 6
 7r = model.encryptWithUserKey("data")
 8model.decryptWithUserKey(r) # Returns b"data"
 9
10## Here is the tricky part:
11
12r = model.encryptWithUserKey("data", ["Test_UserName2"]) # Allow Test_UserName to decrypt the data
13model2.decryptWithUserKey(r[0][1], r[0][2], "Test_UserName") # Returns b"data"

In the case that an incorrect data, or key is provided, a ValueError will be raised.

encryptWithUserKey needs the following parameters: data, otherUsers (optional). data is the plaintext to encrypt and otherUsers is a list of usernames of users who can also decrypt the data.

encryptWithUserKey returns a list of tuples in the following format: (username, data, salt). username is the name of the user to who we need to provide data and salt.

When decrypting, call decryptUserKey, on the user object corresponding to username, passing data as the first argument and salt as the second argument. It will return the plaintext.

Therefore, by using this method, you can grant another user access to some of the user’s data, simply by allowing that user to decrypt the data.

4.6. Password Reset

To enable password reset you need to obtain recovery codes, that you can use to unlock the account.

1keys = model.enablePWDReset() # keys is a list of OTPs that can be used to unlock the user account
2model.logout() # This is not needed but you can reset the password of a locked out user.
3sessionKey = model.resetPWD(keys[0], "newPWD") # Note: you cannot use keys[0] again, use the next one in the list.
4# Note: when you call resetPWD the model will automatically login, you may want to logout
5model.logout()

If a wrong code is provided, Krptn will impose a 5 second delay to slow brute force attacks. However, please note that is not enough to fully protect you. Therefore, it is necessary to impose a proper rate limiting solution on your webserver.

You may notice in the previous code block the resetPWD returns a sessionKey. This session key is the same as returned from the model.login method.

If the OTPs get compromised you can revoke them and generate new ones:

1model.disablePWDReset() # Revoke
2keys = model.enablePWDReset() # Generate. This also revokes all codes but we already did so previously.