oblador / react-native-keychain

:key: Keychain Access for React Native
MIT License
3.2k stars 520 forks source link

How to follow best practice with this package (i.e. not using username/password) ? #291

Open activebiz opened 4 years ago

activebiz commented 4 years ago

Hi,

I am new to this package so apologies in advance if this sounds basic question. I understand the following statement which is on the readme.md.

Follow best practices and do not store user credentials on a device. Instead use tokens or other forms of authentication and re-ask for user credentials before performing sensitive operations.

However all the methods in the documentation and in he keychainexample is using username/passwords. So I am not sure how do I use tokens to store the sensitive data to the device using this library?!

Thanks

Brqqq commented 4 years ago

The username/password examples are just to make the API usage clear I think.

What you need to store depends on your particular situation. Think of it like this: what can you store so that if there is a security breach, the impact will be minimal?

Token-based approach is not about this library, but about your login system. If you use oauth2 on your service/website for example, you will likely get some access token after logging in. This access token is sent with each API call to show the server you're authenticated. It can also be revoked. So you should store the access token rather than username/password.

In the event of a security breach, you can simply revoke the token and there will be no further damage. If the attacker gets a username/password, the impact is very big. They can for example try this username/password on other services. Storing passwords should not be taken lightly.

The other question to ask is: should you be storing this info at all? Why do you want to store a password? Wouldn't it be safer if the user just has to re-type their password? Maybe you can avoid the whole issue in the first place.

louiszawadzki commented 4 years ago

To add to @Brqqq's answer, if you want to authenticate users on your server and tokens is not a solution (for whatever reason), maybe you should consider following what react-native-biometrics recommends: https://github.com/SelfLender/react-native-biometrics#usage.

When a user enrolls in biometrics, a key pair is generated. The private key is stored securely on the device and the public key is sent to a server for registration. When the user wishes to authenticate, the user is prompted for biometrics, which unlocks the securely stored private key. Then a cryptographic signature is generated and sent to the server for verification. The server then verifies the signature. If the verification was successful, the server returns an appropriate response and authorizes the user.

See also https://android-developers.googleblog.com/2015/10/new-in-android-samples-authenticating.html

paulosborne commented 4 years ago

So you should store the access token rather than username/password.

I think the OP is actually asking what is the best practice for securely storing arbitrary data (such as a token) using this library. The API seems to be heavily focused on storing username and passwords. For example, the method setGenericPassword has the following signature

setGenericPassword(username, password, [{ accessControl, accessible, accessGroup, service, securityLevel }])

That seems more than just an example to make usage clearer, I would expect something more like

setItem(key, value, options)
getItem(key)
Aung-Myint-Thein commented 4 years ago

I have 2 values (tokens) that i would like to store but I am not sure what is the best way to store them.. any recommendations?

Iltimirov commented 4 years ago

I found two ways how it can be implemented (but both are some kind of hack)

Interface

type SetSecureValue = (key: string, value: string) => Promise<void>
type GetSecureValue = (key: string) => Promise<string | false>
type RemoveSecureValue = (key: string) => Promise<void>
  1. With generic password (use optional service param)
    
    const setSecureValue: SetSecureValue = (key, value) => 
    Keychain.setGenericPassword(key /* <- can be a random string */, value, { service: key })

const getSecureValue: GetSecureValue = async (key) => { const result = await Keychain.getGenericPassword({ service: key }) if (result) { return result.password } return false }

const removeSecureValue: RemoveSecureValue = (key) => Keychain.resetGenericPassword({ service: key })


2. With internet credentials
```typescript
const setSecureValue: SetSecureValue = (key, value) =>
  Keychain.setInternetCredentials(key, key /* <- can be a random string */, value)

const getSecureValue: GetSecureValue = async (key) => {
  const result = await Keychain.getInternetCredentials(key)
  if (result) {
    return result.password
  }
  return false
}

const removeSecureValue: RemoveSecureValue = (key) =>
  Keychain.resetInternetCredentials(key)

p.s. There is no options in examples, but it shouldn't be a problem to add them

pke commented 3 years ago

This should really make it into the README.md

jtvargas commented 3 years ago

What would be the difference between saving with setGenericPassword and setInternetCredentials?

horstleung commented 3 years ago

What would be the difference between saving with setGenericPassword and setInternetCredentials?

almost the same. the internetCredentials has more attributes to distinguish the remote access. (i.e. The generic one cannot save the server attribute) image

jtvargas commented 3 years ago

@fattomhk So, if my app use credentials that are also used for a web portal, I need to use setInternetCredentials and add the server attribute (server will be web portal URL?)

horstleung commented 3 years ago

@fattomhk So, if my app use credentials that are also used for a web portal, I need to use setInternetCredentials and add the server attribute (server will be web portal URL?)

yes, server should be the domain name or IP address of url (etc. github.com).

jtvargas commented 3 years ago

@fattomhk If I use setInternetCredentials on iOS and in the key server I use my domain name (etc. www.exampledomainservervalue.com), those credentials will appear on my phone keychain (Settings > Passwords) and can be modified via phone settings?

horstleung commented 3 years ago

@fattomhk If I use setInternetCredentials on iOS and in the key server I use my domain name (etc. www.exampledomainservervalue.com), those credentials will appear on my phone keychain (Settings > Passwords) and can be modified via phone settings?

No. If you want to do that, in short, you need to use setSharedWebCredentials and requestSharedWebCredentials Normal flow: image

jtvargas commented 3 years ago

@fattomhk If I use setInternetCredentials on iOS and in the key server I use my domain name (etc. www.exampledomainservervalue.com), those credentials will appear on my phone keychain (Settings > Passwords) and can be modified via phone settings?

No. If you want to do that, in short, you need to use setSharedWebCredentials and requestSharedWebCredentials

Normal flow:

image

But to those functions are only available on iOS, so for Android use “setInternetCredentials”?

horstleung commented 3 years ago

But to those functions are only available on iOS, so for Android use “setInternetCredentials”?

AFAIK, this lib cannot share pwd to another app (correct me if it support) or to web (Android) . If you want to implement it, maybe smartlock-passwords is the thing you are looking for.

pke commented 3 years ago

smartlock-password is an android tech?

horstleung commented 3 years ago

smartlock-password is an android tech?

yes, android only. A Google play service tech, required Android 4.4+ with play service 15+

jonra1993 commented 3 years ago

Hello, @Iltimirov thanks your solution worked perfectly on my current app for saving access and refresh tokens on JWT authentication.

I used your code and I added its current library types

import * as Keychain from 'react-native-keychain';

type SetSecureValue = (key: string, value: string) => Promise<false | Keychain.Result>
type GetSecureValue = (key: string) => Promise<string | false>
type RemoveSecureValue = (key: string) => Promise<boolean>

export const setSecureValue: SetSecureValue = async (key, value) => 
  await Keychain.setGenericPassword(key /* <- can be a random string */, value, { service: key })

export const getSecureValue: GetSecureValue = async (key) => {
  const result = await Keychain.getGenericPassword({ service: key })
  if (result) {
    return result.password
  }
  return false
}

export const removeSecureValue: RemoveSecureValue = async (key) =>
  await Keychain.resetGenericPassword({ service: key })
rosem commented 2 years ago

Is there an advantage to this over just using setGenericPassword directly? I guess API clarity?