Handling API 403 and 401 Unauthorized errors is essential when building secure Kotlin Android applications that rely on web services. These HTTP status codes indicate that the user is either unauthorized (401) or forbidden (403) to access a resource, usually due to invalid credentials or missing access tokens.
In Kotlin-based Android development using Retrofit
, the best practice is to intercept these responses, notify the user, and redirect them to login or token refresh logic.
Here is a complete working example to handle 401/403 API errors using Retrofit and OkHttp interceptor in Kotlin.
Step 1: Add Retrofit & OkHttp dependencies (in build.gradle
):
implementation "com.squareup.retrofit2:retrofit:2.9.0"
implementation "com.squareup.okhttp3:logging-interceptor:4.9.1"
implementation "com.squareup.retrofit2:retrofit:2.9.0"
implementation "com.squareup.okhttp3:logging-interceptor:4.9.1"
implementation "com.squareup.retrofit2:retrofit:2.9.0"
implementation "com.squareup.okhttp3:logging-interceptor:4.9.1"
Step 2: Create an AuthInterceptor.kt
class AuthInterceptor(private val tokenProvider: () -> String?) : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val token = tokenProvider()
val request = chain.request().newBuilder()
if (!token.isNullOrEmpty()) {
header("Authorization", "Bearer $token")
val response = chain.proceed(request)
if (response.code == 401 || response.code == 403) {
// You can log out the user or refresh token here
println("Unauthorized or Forbidden. Code: ${response.code}")
class AuthInterceptor(private val tokenProvider: () -> String?) : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val token = tokenProvider()
val request = chain.request().newBuilder()
.apply {
if (!token.isNullOrEmpty()) {
header("Authorization", "Bearer $token")
}
}
.build()
val response = chain.proceed(request)
if (response.code == 401 || response.code == 403) {
// You can log out the user or refresh token here
println("Unauthorized or Forbidden. Code: ${response.code}")
}
return response
}
}
class AuthInterceptor(private val tokenProvider: () -> String?) : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val token = tokenProvider()
val request = chain.request().newBuilder()
.apply {
if (!token.isNullOrEmpty()) {
header("Authorization", "Bearer $token")
}
}
.build()
val response = chain.proceed(request)
if (response.code == 401 || response.code == 403) {
// You can log out the user or refresh token here
println("Unauthorized or Forbidden. Code: ${response.code}")
}
return response
}
}
Step 3: Create Retrofit Instance (ApiClient.kt
)
private val retrofit: Retrofit
val client = OkHttpClient.Builder()
.addInterceptor(AuthInterceptor { getTokenFromPrefs() })
retrofit = Retrofit.Builder()
.baseUrl("https://api.example.com/")
.addConverterFactory(GsonConverterFactory.create())
fun <T> create(service: Class<T>): T = retrofit.create(service)
private fun getTokenFromPrefs(): String? {
// Replace this with real SharedPreferences or DataStore logic
object ApiClient {
private val retrofit: Retrofit
init {
val client = OkHttpClient.Builder()
.addInterceptor(AuthInterceptor { getTokenFromPrefs() })
.build()
retrofit = Retrofit.Builder()
.baseUrl("https://api.example.com/")
.client(client)
.addConverterFactory(GsonConverterFactory.create())
.build()
}
fun <T> create(service: Class<T>): T = retrofit.create(service)
private fun getTokenFromPrefs(): String? {
// Replace this with real SharedPreferences or DataStore logic
return "your_token_here"
}
}
object ApiClient {
private val retrofit: Retrofit
init {
val client = OkHttpClient.Builder()
.addInterceptor(AuthInterceptor { getTokenFromPrefs() })
.build()
retrofit = Retrofit.Builder()
.baseUrl("https://api.example.com/")
.client(client)
.addConverterFactory(GsonConverterFactory.create())
.build()
}
fun <T> create(service: Class<T>): T = retrofit.create(service)
private fun getTokenFromPrefs(): String? {
// Replace this with real SharedPreferences or DataStore logic
return "your_token_here"
}
}
Step 4: Define Your API Interface (ApiService.kt
)
suspend fun getUserProfile(): Response<UserProfile>
interface ApiService {
@GET("user/profile")
suspend fun getUserProfile(): Response<UserProfile>
}
interface ApiService {
@GET("user/profile")
suspend fun getUserProfile(): Response<UserProfile>
}
Step 5: Use the API in Your ViewModel or Repository
val api = ApiClient.create(ApiService::class.java)
val response = api.getUserProfile()
if (response.isSuccessful) {
val user = response.body()
// Handle other error codes
Log.e("API_ERROR", "Error: ${response.code()}")
val api = ApiClient.create(ApiService::class.java)
viewModelScope.launch {
val response = api.getUserProfile()
if (response.isSuccessful) {
val user = response.body()
// Handle success
} else {
// Handle other error codes
Log.e("API_ERROR", "Error: ${response.code()}")
}
}
val api = ApiClient.create(ApiService::class.java)
viewModelScope.launch {
val response = api.getUserProfile()
if (response.isSuccessful) {
val user = response.body()
// Handle success
} else {
// Handle other error codes
Log.e("API_ERROR", "Error: ${response.code()}")
}
}
This setup ensures you handle 401/403 errors in a centralized manner using interceptors. It also prepares your codebase for further enhancements like token refresh workflows or user logout.
For more best practices on secure API integration, check out Retrofit’s official documentation.
Post Views: 30