Skip to content

Commit 67c2390

Browse files
feat: Implement JWT refresh token for authorization (#2230)
1 parent f0d8f01 commit 67c2390

File tree

7 files changed

+121
-13
lines changed

7 files changed

+121
-13
lines changed
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package org.fossasia.openevent.general.auth
2+
3+
import okhttp3.Authenticator
4+
import okhttp3.Request
5+
import okhttp3.Response
6+
import okhttp3.Route
7+
import org.koin.core.KoinComponent
8+
import org.koin.core.inject
9+
10+
class TokenAuthenticator : Authenticator, KoinComponent {
11+
12+
val tokenService: RefreshTokenService by inject()
13+
val authHolder: AuthHolder by inject()
14+
15+
/**
16+
* Authenticator for when the authToken need to be refresh and updated
17+
* everytime we get a 401 error code
18+
*/
19+
20+
override fun authenticate(route: Route?, response: Response): Request? {
21+
22+
val loginResponse = tokenService.refreshToken()
23+
24+
return if (loginResponse.isSuccessful) {
25+
/**
26+
* Replace the existing tokens with the new tokens
27+
**/
28+
loginResponse.body()?.let {
29+
authHolder.accessToken = it.accessToken
30+
authHolder.refreshToken = it.refreshToken
31+
32+
val newToken = "JWT ${it.accessToken}"
33+
34+
response.request.newBuilder()
35+
.addHeader("Authorization", newToken)
36+
.build()
37+
}
38+
} else {
39+
response.request
40+
}
41+
}
42+
}

app/src/main/java/org/fossasia/openevent/general/auth/AuthApi.kt

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,22 @@ import org.fossasia.openevent.general.auth.change.ChangeRequestTokenResponse
77
import org.fossasia.openevent.general.auth.forgot.Email
88
import org.fossasia.openevent.general.auth.forgot.RequestToken
99
import org.fossasia.openevent.general.auth.forgot.RequestTokenResponse
10+
import retrofit2.Response
1011
import retrofit2.http.Body
12+
import retrofit2.http.DELETE
1113
import retrofit2.http.GET
1214
import retrofit2.http.PATCH
1315
import retrofit2.http.POST
1416
import retrofit2.http.Path
15-
import retrofit2.http.DELETE
1617

1718
interface AuthApi {
1819

19-
@POST("../auth/session")
20+
@POST("/auth/login")
2021
fun login(@Body login: Login): Single<LoginResponse>
2122

23+
@POST("/auth/token/refresh")
24+
fun refreshToken(): Response<LoginResponse>
25+
2226
@GET("users/{id}")
2327
fun getProfile(@Path("id") id: Long): Single<User>
2428

app/src/main/java/org/fossasia/openevent/general/auth/AuthHolder.kt

Lines changed: 33 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,37 +3,62 @@ package org.fossasia.openevent.general.auth
33
import org.fossasia.openevent.general.data.Preference
44
import org.fossasia.openevent.general.utils.JWTUtils
55

6-
private const val TOKEN_KEY = "TOKEN"
6+
private const val ACCESS_TOKEN = "accessToken"
7+
private const val REFRESH_TOKEN = "refreshToken"
78

89
class AuthHolder(private val preference: Preference) {
910

10-
var token: String? = null
11+
var accessToken: String? = null
1112
get() {
12-
return preference.getString(TOKEN_KEY)
13+
return preference.getString(ACCESS_TOKEN)
1314
}
1415
set(value) {
1516
if (value != null && JWTUtils.isExpired(value))
1617
throw IllegalStateException("Cannot set expired token")
1718
field = value
18-
preference.putString(TOKEN_KEY, value)
19+
preference.putString(ACCESS_TOKEN, value)
1920
}
21+
var refreshToken: String? = null
22+
get() {
23+
return preference.getString(REFRESH_TOKEN)
24+
}
25+
set(value) {
26+
field = if (value != null && JWTUtils.isExpired(value)) {
27+
"expired"
28+
} else {
29+
value
30+
}
31+
preference.putString(REFRESH_TOKEN, value)
32+
}
33+
34+
fun getAccessAuthorization(): String? {
35+
if (!isLoggedIn())
36+
return null
37+
return "JWT $accessToken"
38+
}
39+
40+
fun getRefreshAuthorization(): String? {
41+
if (!isLoggedIn())
42+
return null
43+
return "JWT $refreshToken"
44+
}
2045

2146
fun getAuthorization(): String? {
2247
if (!isLoggedIn())
2348
return null
24-
return "JWT $token"
49+
return "JWT $accessToken"
2550
}
2651

2752
fun isLoggedIn(): Boolean {
28-
if (token == null || JWTUtils.isExpired(token)) {
29-
token = null
53+
if (accessToken == null || JWTUtils.isExpired(accessToken)) {
54+
accessToken = null
3055
return false
3156
}
3257

3358
return true
3459
}
3560

3661
fun getId(): Long {
37-
return if (!isLoggedIn()) -1 else JWTUtils.getIdentity(token)
62+
return if (!isLoggedIn()) -1 else JWTUtils.getIdentity(accessToken)
3863
}
3964
}

app/src/main/java/org/fossasia/openevent/general/auth/AuthService.kt

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,9 @@ class AuthService(
2929

3030
return authApi.login(Login(username, password))
3131
.map {
32-
authHolder.token = it.accessToken
32+
authHolder.accessToken = it.accessToken
33+
authHolder.refreshToken = it.refreshToken
34+
3335
it
3436
}
3537
}
@@ -66,7 +68,8 @@ class AuthService(
6668

6769
fun logout(): Completable {
6870
return Completable.fromAction {
69-
authHolder.token = null
71+
authHolder.accessToken = null
72+
authHolder.refreshToken = null
7073
userDao.deleteUser(authHolder.getId())
7174
orderDao.deleteAllOrders()
7275
attendeeDao.deleteAllAttendees()

app/src/main/java/org/fossasia/openevent/general/auth/LoginResponse.kt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,7 @@ import com.fasterxml.jackson.databind.PropertyNamingStrategy
44
import com.fasterxml.jackson.databind.annotation.JsonNaming
55

66
@JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy::class)
7-
data class LoginResponse(val accessToken: String)
7+
data class LoginResponse(
8+
val accessToken: String,
9+
val refreshToken: String
10+
)
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package org.fossasia.openevent.general.auth
2+
3+
import io.reactivex.Single
4+
import org.fossasia.openevent.general.BuildConfig
5+
import retrofit2.Response
6+
import retrofit2.Retrofit
7+
import retrofit2.converter.gson.GsonConverterFactory
8+
9+
class RefreshTokenService(
10+
private val authHolder: AuthHolder
11+
) {
12+
13+
private val retrofit = Retrofit.Builder()
14+
.addConverterFactory(GsonConverterFactory.create())
15+
.baseUrl(BuildConfig.DEFAULT_BASE_URL)
16+
.build()
17+
18+
19+
private val authApi: AuthApi = retrofit.create(AuthApi::class.java)
20+
21+
fun refreshToken(): Response<LoginResponse> {
22+
return authApi.refreshToken()
23+
}
24+
25+
26+
}

app/src/main/java/org/fossasia/openevent/general/di/Modules.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import org.fossasia.openevent.general.auth.SignUp
3030
import org.fossasia.openevent.general.auth.SignUpViewModel
3131
import org.fossasia.openevent.general.auth.User
3232
import org.fossasia.openevent.general.auth.AuthViewModel
33+
import org.fossasia.openevent.general.auth.RefreshTokenService
3334
import org.fossasia.openevent.general.data.Network
3435
import org.fossasia.openevent.general.data.Preference
3536
import org.fossasia.openevent.general.event.Event
@@ -71,6 +72,7 @@ import org.fossasia.openevent.general.search.location.LocationService
7172
import org.fossasia.openevent.general.search.type.SearchTypeViewModel
7273
import org.fossasia.openevent.general.search.location.LocationServiceImpl
7374
import org.fossasia.openevent.general.auth.SmartAuthViewModel
75+
import org.fossasia.openevent.general.auth.TokenAuthenticator
7476
import org.fossasia.openevent.general.connectivity.MutableConnectionLiveData
7577
import org.fossasia.openevent.general.discount.DiscountApi
7678
import org.fossasia.openevent.general.discount.DiscountCode
@@ -231,6 +233,8 @@ val apiModule = module {
231233
factory { FeedbackService(get(), get()) }
232234
factory { SettingsService(get(), get()) }
233235
factory { TaxService(get(), get()) }
236+
factory { RefreshTokenService(get()) }
237+
234238
}
235239

236240
val viewModelModule = module {
@@ -296,6 +300,7 @@ val networkModule = module {
296300
.readTimeout(readTimeout.toLong(), TimeUnit.SECONDS)
297301
.addInterceptor(HostSelectionInterceptor(get()))
298302
.addInterceptor(RequestAuthenticator(get()))
303+
.authenticator(TokenAuthenticator())
299304
.addNetworkInterceptor(StethoInterceptor())
300305

301306
if (BuildConfig.DEBUG) {

0 commit comments

Comments
 (0)