Skip to content

Commit

Permalink
feat: Implement JWT refresh token for authorization (fossasia#2230)
Browse files Browse the repository at this point in the history
  • Loading branch information
aggarwalpulkit596 committed Sep 18, 2019
1 parent f0d8f01 commit 67c2390
Show file tree
Hide file tree
Showing 7 changed files with 121 additions and 13 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package org.fossasia.openevent.general.auth

import okhttp3.Authenticator
import okhttp3.Request
import okhttp3.Response
import okhttp3.Route
import org.koin.core.KoinComponent
import org.koin.core.inject

class TokenAuthenticator : Authenticator, KoinComponent {

val tokenService: RefreshTokenService by inject()
val authHolder: AuthHolder by inject()

/**
* Authenticator for when the authToken need to be refresh and updated
* everytime we get a 401 error code
*/

override fun authenticate(route: Route?, response: Response): Request? {

val loginResponse = tokenService.refreshToken()

return if (loginResponse.isSuccessful) {
/**
* Replace the existing tokens with the new tokens
**/
loginResponse.body()?.let {
authHolder.accessToken = it.accessToken
authHolder.refreshToken = it.refreshToken

val newToken = "JWT ${it.accessToken}"

response.request.newBuilder()
.addHeader("Authorization", newToken)
.build()
}
} else {
response.request
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,22 @@ import org.fossasia.openevent.general.auth.change.ChangeRequestTokenResponse
import org.fossasia.openevent.general.auth.forgot.Email
import org.fossasia.openevent.general.auth.forgot.RequestToken
import org.fossasia.openevent.general.auth.forgot.RequestTokenResponse
import retrofit2.Response
import retrofit2.http.Body
import retrofit2.http.DELETE
import retrofit2.http.GET
import retrofit2.http.PATCH
import retrofit2.http.POST
import retrofit2.http.Path
import retrofit2.http.DELETE

interface AuthApi {

@POST("../auth/session")
@POST("/auth/login")
fun login(@Body login: Login): Single<LoginResponse>

@POST("/auth/token/refresh")
fun refreshToken(): Response<LoginResponse>

@GET("users/{id}")
fun getProfile(@Path("id") id: Long): Single<User>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,37 +3,62 @@ package org.fossasia.openevent.general.auth
import org.fossasia.openevent.general.data.Preference
import org.fossasia.openevent.general.utils.JWTUtils

private const val TOKEN_KEY = "TOKEN"
private const val ACCESS_TOKEN = "accessToken"
private const val REFRESH_TOKEN = "refreshToken"

class AuthHolder(private val preference: Preference) {

var token: String? = null
var accessToken: String? = null
get() {
return preference.getString(TOKEN_KEY)
return preference.getString(ACCESS_TOKEN)
}
set(value) {
if (value != null && JWTUtils.isExpired(value))
throw IllegalStateException("Cannot set expired token")
field = value
preference.putString(TOKEN_KEY, value)
preference.putString(ACCESS_TOKEN, value)
}
var refreshToken: String? = null
get() {
return preference.getString(REFRESH_TOKEN)
}
set(value) {
field = if (value != null && JWTUtils.isExpired(value)) {
"expired"
} else {
value
}
preference.putString(REFRESH_TOKEN, value)
}

fun getAccessAuthorization(): String? {
if (!isLoggedIn())
return null
return "JWT $accessToken"
}

fun getRefreshAuthorization(): String? {
if (!isLoggedIn())
return null
return "JWT $refreshToken"
}

fun getAuthorization(): String? {
if (!isLoggedIn())
return null
return "JWT $token"
return "JWT $accessToken"
}

fun isLoggedIn(): Boolean {
if (token == null || JWTUtils.isExpired(token)) {
token = null
if (accessToken == null || JWTUtils.isExpired(accessToken)) {
accessToken = null
return false
}

return true
}

fun getId(): Long {
return if (!isLoggedIn()) -1 else JWTUtils.getIdentity(token)
return if (!isLoggedIn()) -1 else JWTUtils.getIdentity(accessToken)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@ class AuthService(

return authApi.login(Login(username, password))
.map {
authHolder.token = it.accessToken
authHolder.accessToken = it.accessToken
authHolder.refreshToken = it.refreshToken

it
}
}
Expand Down Expand Up @@ -66,7 +68,8 @@ class AuthService(

fun logout(): Completable {
return Completable.fromAction {
authHolder.token = null
authHolder.accessToken = null
authHolder.refreshToken = null
userDao.deleteUser(authHolder.getId())
orderDao.deleteAllOrders()
attendeeDao.deleteAllAttendees()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,7 @@ import com.fasterxml.jackson.databind.PropertyNamingStrategy
import com.fasterxml.jackson.databind.annotation.JsonNaming

@JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy::class)
data class LoginResponse(val accessToken: String)
data class LoginResponse(
val accessToken: String,
val refreshToken: String
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package org.fossasia.openevent.general.auth

import io.reactivex.Single
import org.fossasia.openevent.general.BuildConfig
import retrofit2.Response
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory

class RefreshTokenService(
private val authHolder: AuthHolder
) {

private val retrofit = Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create())
.baseUrl(BuildConfig.DEFAULT_BASE_URL)
.build()


private val authApi: AuthApi = retrofit.create(AuthApi::class.java)

fun refreshToken(): Response<LoginResponse> {
return authApi.refreshToken()
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import org.fossasia.openevent.general.auth.SignUp
import org.fossasia.openevent.general.auth.SignUpViewModel
import org.fossasia.openevent.general.auth.User
import org.fossasia.openevent.general.auth.AuthViewModel
import org.fossasia.openevent.general.auth.RefreshTokenService
import org.fossasia.openevent.general.data.Network
import org.fossasia.openevent.general.data.Preference
import org.fossasia.openevent.general.event.Event
Expand Down Expand Up @@ -71,6 +72,7 @@ import org.fossasia.openevent.general.search.location.LocationService
import org.fossasia.openevent.general.search.type.SearchTypeViewModel
import org.fossasia.openevent.general.search.location.LocationServiceImpl
import org.fossasia.openevent.general.auth.SmartAuthViewModel
import org.fossasia.openevent.general.auth.TokenAuthenticator
import org.fossasia.openevent.general.connectivity.MutableConnectionLiveData
import org.fossasia.openevent.general.discount.DiscountApi
import org.fossasia.openevent.general.discount.DiscountCode
Expand Down Expand Up @@ -231,6 +233,8 @@ val apiModule = module {
factory { FeedbackService(get(), get()) }
factory { SettingsService(get(), get()) }
factory { TaxService(get(), get()) }
factory { RefreshTokenService(get()) }

}

val viewModelModule = module {
Expand Down Expand Up @@ -296,6 +300,7 @@ val networkModule = module {
.readTimeout(readTimeout.toLong(), TimeUnit.SECONDS)
.addInterceptor(HostSelectionInterceptor(get()))
.addInterceptor(RequestAuthenticator(get()))
.authenticator(TokenAuthenticator())
.addNetworkInterceptor(StethoInterceptor())

if (BuildConfig.DEBUG) {
Expand Down

0 comments on commit 67c2390

Please sign in to comment.