I can't receive tokens on android app from the django server
I am trying to figure out why I can not receive access tokens on my android app from the django server. The android app structure seems fine to me. Still the issue persists.
I tested the server response with postman and it looks fine (I receive both, access and refresh tokens) but on my android app that does not work. When I call the login function from the app, the server response is 200, but I have no tokens saved in the xml file. I share with you some code snippets hoping someone here could help me. Thank you in advance anybody!
LOGIN FUNCTION
suspend fun loginUser(userService: UserService, email: String, password: String, context: Context, navController: NavController) {
try {
val response = userService.loginUser(UserLoginRequest(email, password)).await()
if (response.isSuccessful) {
val tokenResponse = response.body()!!
TokenManager.saveTokens(tokenResponse.accessToken, tokenResponse.refreshToken)
withContext(Dispatchers.Main) {
navController.navigate(Screen.FeedScreen.route)
}
} else {
withContext(Dispatchers.Main) {
Toast.makeText(context, "Login failed: ${response.message()}", Toast.LENGTH_LONG).show()
}
}
} catch (e: Exception) {
withContext(Dispatchers.Main) {
Toast.makeText(context, "An error occurred: ${e.localizedMessage}", Toast.LENGTH_LONG).show()
}
}
}
TOKEN MANAGER
package com.example.events.util
import android.content.SharedPreferences
import android.util.Log
import com.example.events.network.NetworkModule
import com.example.events.network.RefreshRequestBody
import com.example.events.network.RefreshTokenResponse
import com.example.events.network.TokenRefreshService
import com.google.gson.Gson
object TokenManager {
private lateinit var preferences: SharedPreferences
// Initialise with SharedPreferences
fun init(preferences: SharedPreferences) {
this.preferences = preferences
}
val accessToken: String?
get() = preferences.getString("access", null)
fun isTokenExpired(): Boolean {
val expiry = preferences.getLong("expiry", 0)
return expiry < System.currentTimeMillis()
}
fun refreshToken(): String? {
val refreshTokenValue = preferences.getString("refresh", null) ?: return null
val refreshRequestBody = RefreshRequestBody(refreshTokenValue)
try {
val call = NetworkModule.retrofit.create(TokenRefreshService::class.java)
.refreshToken(refreshRequestBody)
val response = call.execute()
if (response.isSuccessful) {
val refreshTokenResponse =
Gson().fromJson(response.body()?.string(), RefreshTokenResponse::class.java)
refreshTokenResponse?.let {
with(preferences.edit()) {
putString("access", it.access)
putString("refresh", it.refresh)
putLong(
"expiry",
System.currentTimeMillis() + 1000 * 60 * 5
) // Adjust based on actual token expiry from backend
apply()
}
return it.access
}
}
} catch (e: Exception) {
e.printStackTrace()
}
// No need for finally block to close the ResponseBody when using Retrofit
return null
}
fun saveTokens(accessToken: String, refreshToken: String) {
if (accessToken.isNotEmpty() && refreshToken.isNotEmpty()) {
preferences.edit().apply {
putString("access", accessToken)
putString("refresh", refreshToken)
apply()
}
} else {
Log.e("TokenManager", "Attempted to save null or empty tokens.")
}
}
}
USER SERVICE FUNCTION
@Headers("Content-Type: application/json")
@POST("/users/login/")
fun loginUser(@Body data: UserLoginRequest): Deferred<Response<TokenResponse>>
NETWORK MODULE
package com.example.events.network
import com.jakewharton.retrofit2.adapter.kotlin.coroutines.CoroutineCallAdapterFactory
import okhttp3.OkHttpClient
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import java.net.CookieManager
import java.net.CookiePolicy
object NetworkModule {
private const val BASE_URL = "http://192.168.0.100:8000/"
val retrofit: Retrofit by lazy {
/*val cookieManager = CookieManager().apply {
setCookiePolicy(CookiePolicy.ACCEPT_ALL)
}*/
val client = OkHttpClient.Builder()
.addInterceptor(AuthInterceptor()) // Add the AuthInterceptor
// .cookieJar(JavaNetCookieJar(cookieManager)) // Add Cookie Manager
.build()
Retrofit.Builder()
.baseUrl(BASE_URL)
.client(client)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(CoroutineCallAdapterFactory())
.build()
}
}
AUTH INTERCEPTOR
package com.example.events.network
import com.example.events.util.TokenManager
import okhttp3.Interceptor
import okhttp3.Response
class AuthInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val accessToken = TokenManager.accessToken
val requestBuilder = chain.request().newBuilder()
// Add the Authorization header
accessToken?.let {
requestBuilder.addHeader("Authorization", "Bearer $it")
}
// Proceed with the original request
val originalResponse = chain.proceed(requestBuilder.build())
// Check if the token is expired after receiving the response
if (TokenManager.isTokenExpired()) {
synchronized(this) {
// Close the original response to avoid IllegalStateException
originalResponse.close()
val newAccessToken = TokenManager.refreshToken()
newAccessToken?.let {
// Add the new access token to the Authorization header
requestBuilder.addHeader("Authorization", "Bearer $it")
}
// Retry the request with the new access token
return chain.proceed(requestBuilder.build())
}
}
// Return the original response if the token was not expired
return originalResponse
}
}
TOKEN RESPONSE
package com.example.events.model
data class TokenResponse(
val accessToken: String,
val refreshToken: String
)
USER LOGIN REQUEST
package com.example.events.model.user
data class UserLoginRequest(
val email: String, // or username, in future
val password: String
)