about JWT Authentication and Refresh Token in Android will lid the most recent and most present steering in relation to the world. entry slowly appropriately you comprehend with out issue and appropriately. will enhance your information dexterously and reliably
On this article, we are going to implement JWT (JSON Net Token) authentication and silent replace with the Retrofit Interceptor and Authenticator. Silent token refresh is required when the token expires and the server response is 401 Unauthorized. As a substitute of logging the person out, we’ll refresh the token and proceed with the request.
Desk of Contents
We’ll be utilizing LiveData, Hilt, Retrofit, OkHttp, and DataStore on this article, so that you higher know the way they work.
I will skip some components of the Hilt setup, so if you would like to see the supply code, you will discover the hyperlink on the backside of this text.
software stage construct.gradle
proceedings,
//DataStore
implementation "androidx.datastore:datastore-preferences:1.0.0"//Retrofit
def retrofit_version = "2.9.0"
implementation "com.squareup.retrofit2:retrofit:$retrofit_version"
implementation "com.squareup.retrofit2:converter-gson:$retrofit_version"
//Hilt
def hilt_version = "2.44"
implementation "com.google.dagger:hilt-android:$hilt_version"
kapt "com.google.dagger:hilt-compiler:$hilt_version"
//OkHttp
def okhttp_version = "4.10.0"
implementation "com.squareup.okhttp3:okhttp:$okhttp_version"
implementation "com.squareup.okhttp3:logging-interceptor:$okhttp_version"
Remember so as to add web permission in AndroidManifest.xml
,
<uses-permission android:title="android.permission.INTERNET" />
I created my very own backend to check token authentication. You’ll be able to examine it from this hyperlink. MrNtlu/JWT-Check-API (github.com)
Additionally, if you wish to run it regionally, you possibly can observe these directions. It’s a quite simple Relaxation API. The token shall be refreshed each 30 seconds and we should refresh the previous token with the brand new one.
We might want to create a pair of request/response fashions for the Relaxation API,
TO POST auth/login
software,
knowledge class Auth(
@SerializedName("email_address")
val e mail: String,
val password: String
)
TO POST auth/login
and get auth/refresh
reply,
knowledge class LoginResponse(
@SerializedName("access_token")
val token: String
)
GET person/information
reply,
Put them in numerous recordsdata. I’ve put them in a code block to make it simpler to learn.
knowledge class UserInfoResponse(
@SerializedName("knowledge")
val userInfo: UserInfo,
val message: String
)
knowledge class UserInfo(
val _id: String,
val email_address: String
)
Lastly ErrorResponse
,
knowledge class ErrorResponse(
val code: Int,
val message: String
)
We could have two separate API service recordsdata. the primary is AuthApiService
,
interface AuthApiService
@POST("auth/login")
droop enjoyable login(
@Physique auth: Auth,
): Response<LoginResponse>@GET("auth/refresh")
droop enjoyable refreshToken(
@Header("Authorization") token: String,
): Response<LoginResponse>
the second is MainApiService
,
interface MainApiService
@GET("person/information")
droop enjoyable getUserInfo(): Response<UserInfoResponse>
That is all for now, we’ll create the Retrofit occasion later on this article.
When a person logs in and reopens the app, we’ll want a method to save that token and use it. DataStore will assist us with this downside. We’ll retailer the token regionally and use it when wanted.
class TokenManager(non-public val context: Context)
companion object
non-public val TOKEN_KEY = stringPreferencesKey("jwt_token")
enjoyable getToken(): Circulate<String?>
return context.dataStore.knowledge.map preferences ->
preferences[TOKEN_KEY]
droop enjoyable saveToken(token: String)
context.dataStore.edit preferences ->
preferences[TOKEN_KEY] = token
droop enjoyable deleteToken()
context.dataStore.edit preferences ->
preferences.take away(TOKEN_KEY)
That is it. There may be nothing particular. Just a bit word context.dataStore
it will not work and you will note an error. Don’t fret. We’ll add it later within the hilt module half.
Earlier than we begin implementing the Repository and View Mannequin, we’ll create helper lessons.
First ApiResponse
, this can assist us handle API requests and UI state administration. We will show the required person interface to the person and simply extract knowledge.
sealed class ApiResponse<out T>
object Loading: ApiResponse<Nothing>()knowledge class Success<out T>(
val knowledge: T
): ApiResponse<T>()
knowledge class Failure(
val errorMessage: String,
val code: Int,
): ApiResponse<Nothing>()
second is apiRequestFlow
this class will assist us to make API calls on the IO thread and emit
the state. At present, the timeout is ready to twenty seconds, you possibly can change it based on your wants.
enjoyable<T> apiRequestFlow(name: droop () -> Response<T>): Circulate<ApiResponse<T>> = move {
emit(ApiResponse.Loading)withTimeoutOrNull(20000L)
val response = name()
attempt
if (response.isSuccessful)
response.physique()?.let knowledge ->
emit(ApiResponse.Success(knowledge))
else
response.errorBody()?.let error ->
error.shut()
val parsedError: ErrorResponse = Gson().fromJson(error.charStream(), ErrorResponse::class.java)
emit(ApiResponse.Failure(parsedError.message, parsedError.code))
catch (e: Exception)
emit(ApiResponse.Failure(e.message ?: e.toString(), 400))
?: emit(ApiResponse.Failure("Timeout! Please attempt once more.", 408))
}.flowOn(Dispatchers.IO)
Lastly BaseViewModel
this class will assist us catch and file bugs within the appropriate thread and abort the job when crucial.
open class BaseViewModel : ViewModel() {
non-public var mJob: Job? = nullprotected enjoyable <T> baseRequest(liveData: MutableLiveData<T>, errorHandler: CoroutinesErrorHandler, request: () -> Circulate<T>)
mJob = viewModelScope.launch(Dispatchers.IO + CoroutineExceptionHandler _, error ->
viewModelScope.launch(Dispatchers.Principal)
errorHandler.onError(error.localizedMessage ?: "Error occured! Please attempt once more.")
)
request().gather
withContext(Dispatchers.Principal)
liveData.worth = it
override enjoyable onCleared()
tremendous.onCleared()
mJob?.let
if (it.isActive)
it.cancel()
}
interface CoroutinesErrorHandler
enjoyable onError(message:String)
That is it. Now we are able to begin implementing the Repository & ViewModel.
As we’ve got already applied apiRequestFlow
repository lessons shall be very brief.
class AuthRepository @Inject constructor(
non-public val authApiService: AuthApiService,
)
enjoyable login(auth: Auth) = apiRequestFlow
authApiService.login(auth)
class MainRepository @Inject constructor(
non-public val mainApiService: MainApiService,
)
enjoyable getUserInfo() = apiRequestFlow
mainApiService.getUserInfo()
The identical goes for view fashions, the one factor further is LiveData objects. Each view fashions lengthen BaseViewModel
that we created earlier.
@HiltViewModel
class AuthViewModel @Inject constructor(
non-public val authRepository: AuthRepository,
): BaseViewModel() non-public val _loginResponse = MutableLiveData<ApiResponse<LoginResponse>>()
val loginResponse = _loginResponse
enjoyable login(auth: Auth, coroutinesErrorHandler: CoroutinesErrorHandler) = baseRequest(
_loginResponse,
coroutinesErrorHandler
)
authRepository.login(auth)
@HiltViewModel
class MainViewModel @Inject constructor(
non-public val mainRepository: MainRepository,
): BaseViewModel() non-public val _userInfoResponse = MutableLiveData<ApiResponse<UserInfoResponse>>()
val userInfoResponse = _userInfoResponse
enjoyable getUserInfo(coroutinesErrorHandler: CoroutinesErrorHandler) = baseRequest(
_userInfoResponse,
coroutinesErrorHandler,
)
mainRepository.getUserInfo()
Lastly, we’re going to create yet one more view mannequin for Token.
@HiltViewModel
class TokenViewModel @Inject constructor(
non-public val tokenManager: TokenManager,
): ViewModel() val token = MutableLiveData<String?>()
non-public var mJob: Job? = null
init
mJob = viewModelScope.launch
tokenManager.getToken().gather
token.worth = it
enjoyable saveToken(token: String)
viewModelScope.launch
tokenManager.saveToken(token)
enjoyable deleteToken()
viewModelScope.launch
tokenManager.deleteToken()
non-public enjoyable removeObservables()
mJob?.let
if (it.isActive)
it.cancel()
mJob = null
override enjoyable onCleared()
tremendous.onCleared()
removeObservables()
On this view mannequin, we’ve got two variables, token
is a reside knowledge object and mJob
is a coroutine employee object. After we initialize the view mannequin, we’re setting the worth of the token to tokenManager.token
and we set the mJob
variable.
each time we name saveToken
both deleteToken
the token worth shall be up to date on tokenManager.getToken().gather
so we do not have to fret about anything.
removeObservables
The operate is to forestall a number of jobs from working on the identical time, which might trigger issues.
That is it! Now we are able to implement Interceptor and Authenticator.
Interceptor may be very easy,
class AuthInterceptor @Inject constructor(
non-public val tokenManager: TokenManager,
): Interceptor
override enjoyable intercept(chain: Interceptor.Chain): Response
val token = runBlocking
tokenManager.getToken().first()
val request = chain.request().newBuilder()
request.addHeader("Authorization", "Bearer $token")
return chain.proceed(request.construct())
we’re getting the token
since tokenManager
blocking the present thread till it completes with runBlocking
. After that, we add Authorization
header of the present request.
There’s a small downside, what if the token is empty or outdated? The answer is easy, Authenticator.
carry out any with preferential proper authentication earlier than connecting to a proxy server, or reagent authentication after receiving a problem from an origin net server or a proxy server. For extra info, you possibly can click on the hyperlink.
class AuthAuthenticator @Inject constructor(
non-public val tokenManager: TokenManager,
): Authenticator {override enjoyable authenticate(route: Route?, response: Response): Request?
val token = runBlocking
tokenManager.getToken().first()
return runBlocking newToken.physique() == null) //Could not refresh the token, so restart the login course of
tokenManager.deleteToken()
newToken.physique()?.let
tokenManager.saveToken(it.token)
response.request.newBuilder()
.header("Authorization", "Bearer $it.token")
.construct()
non-public droop enjoyable getNewToken(refreshToken: String?): retrofit2.Response<LoginResponse>
val loggingInterceptor = HttpLoggingInterceptor()
loggingInterceptor.stage = HttpLoggingInterceptor.Degree.BODY
val okHttpClient = OkHttpClient.Builder().addInterceptor(loggingInterceptor).construct()
val retrofit = Retrofit.Builder()
.baseUrl("https://jwt-test-api.onrender.com/api/")
.addConverterFactory(GsonConverterFactory.create())
.shopper(okHttpClient)
.construct()
val service = retrofit.create(AuthApiService::class.java)
return service.refreshToken("Bearer $refreshToken")
}
As soon as once more, earlier than we do something, let’s get the token
with runBlocking
. After that, we use getNewToken
operate to request a brand new token with the previous token, then we’re checking if we’ve got retrieved the brand new token efficiently.
If we won’t get a brand new token, we use deleteToken
operate that may activate TokenViewModel
token
reside knowledge and within the UI half we are going to drive the person to log off.
If we efficiently retrieve the brand new refresh token, we’ll refresh the previous token worth with the brand new token and make the request.
As you possibly can see within the picture, we make the request (1) however token
has expired, so we make one other request to auth/refresh
for a brand new token and efficiently retrieve it (2). After that we make the request to person/information
and efficiently get the information (3).
This class doesn’t want a lot rationalization, however some small notes,
- As I discussed earlier, we set the
Context.dataStore
extension on the prime of this class. HttpLogginInterceptor
which logs request and response info.- As a substitute of offering a Retrofit occasion, I supplied
Retrofit.Builder
as a result ofAuthApiService
i cannot useOkHttpClient
howeverMainApiService
Will. To make the separation I’m spending solely theRetrofit.Builder
occasion.
That is it. Now we are able to implement UI.
We could have Login
Y Principal
fragments
The views are misconfigured, ignore them.
In LoginFragment
we’ve got two view fashions, AuthViewModel
Y TokenViewModel
. we’ve got used activityViewModels
as a result of TokenViewModel
must be scoped to Exercise, not Fragment.
activityViewModels()
it is fairly self explanatory. It’s used to restrict your exercise and when your exercise is destroyed,activityViewModels()
shall be destroyed too. Supply hyperlink.
We’re two reside knowledge objects, token
Y loginResponse
. When the person clicks on the loginButton
and does the viewModel.login
request, we are going to retrieve the knowledge.token
since loginResponse
and put it aside When saved, token
will take a look at and when it is set and never null, we’ll navigate to MainFragment
.
MainFragment
is similar to LoginFragment
. On infoButton
click on, we place the order and we observe it. After we obtain the userInfoResponse
we current it to you infoTV
textual content view.
As well as, we’re trying on the tokenViewModel
token
reside knowledge, when the token is ready to null, which means we have to log off and return to the login display screen.
Let’s have a look at the outcomes:
That is it! I hope you may have been useful. 👋👋
full code
MrNtlu/Token-Authentication (github.com)
Sources:
I want the article nearly JWT Authentication and Refresh Token in Android provides sharpness to you and is beneficial for including to your information