Store JWT Token with Coroutines
Tags: kotlin, android, security, programming
Published: 2023-04-05 06:28:18

JWT (JSON Web Token) has become a popular standard for implementing stateless authentication in modern mobile apps. In order to maintain security and avoid session hijacking, it's important to store JWT tokens securely on the client-side.

DataStore is a popular choice for storing tokens as it provides the advantages of shared preferences along with additional coroutines capabilities:

```
// store token (must be called from suspend function)
dataStore.edit { it[KEY_TOKEN] = token }
// read token (returns a flow of token)
dataStore.data.map { it[KEY_TOKEN] }
```

One powerful use case is to navigate to the home or login view on wether the token exists or not:

```
dataStore.data
		.map { it.contains(KEY_TOKEN) }
		.onEach { isAuthenticated ->
				when(isAuthenticated) {
						true -> navController.navigate("home")
						false -> navController.navigate("login")
				}
		}.launchIn(scope)
```

However **DataStore lacks support for encryption**, whereas shared preferences does. This means developers have to choose between the convenience of coroutines and the security of encryption when choosing a method to store their JWT tokens.

![Meme about adroid developer choosing between coroutines and encryption for JWT Token](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2mggwaymq3nqkfzwgm7c.png)

Fortunately, there is a library **[Encrypted DataStore](https://github.com/osipxd/encrypted-datastore)** that **extends DataStore and allows for easy encryption**. While this solution is currently useful, it may become deprecated in the future once DataStore natively implements this functionality.

Enough talk, let's dive into the code.

First, import libraries in your build.gradle (app module):

```
// datastore
implementation("androidx.datastore:datastore-preferences:1.0.0")
// extension for datastore to support encryption
implementation("io.github.osipxd:security-crypto-datastore-preferences:1.0.0-alpha04")
// utility library for datastore encryption
implementation("androidx.security:security-crypto-ktx:1.1.0-alpha05")
```

Then you will need to build your datastore to inject it in your AuthenticationService:

```
val dataStore = PreferenceDataStoreFactory.createEncrypted{
	EncryptedFile.Builder(
		context,
	  // the file should have extension .preferences_pb
		dataStoreFile("filename.preferences_pb"),
	  MasterKey
		  .Builder(context)
	    .setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
	    .build(),
	  EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB
	).build()
}
```

Finally you can add this service to your project:

```
import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.stringPreferencesKey
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.flow.map

class AuthenticationService(
    private val dataStore: DataStore<Preferences>
) {

		// returns a flow of is authenticated state
    fun isAuthenticated(): Flow<Boolean> {
				// flow of token existence from dataStore
        return dataStore.data.map {
		        it.contains(KEY_TOKEN)
				}
		}

		// store new token after sign in or token refresh
    suspend fun store(token: String) {
				// store token to dataStore
        dataStore.edit {
            it[KEY_TOKEN] = token
				}
		}

		// get token for protected API method
    suspend fun getToken(): String {
			return dataStore.data
				  .map { it[KEY_TOKEN] } // get a flow of token from dataStore
          .firstOrNull() // transform flow to suspend
		      ?: throw IllegalArgumentException("no token stored")
    }

		// to call when user logs out or when refreshing the token has failed
    suspend fun onLogout() {
				// remove token from dataStore
        dataStore.edit {
            it.remove(KEY_TOKEN)
				}
		}

    companion object {
        val KEY_TOKEN = stringPreferencesKey("key_token")
    }
}
```

In conclusion, there is now an efficient and secure data storage option available for your JWT tokens, so there's no excuse to settle for anything less! 😊