KNET obsługuje Bearer Token, Basic Auth, OAuth i automatyczny refresh tokenów.
// Per request
val response = client.get(
url = "https://api.example.com/users",
headers = mapOf("Authorization" to "Bearer $token")
)
// Interceptor - wszystkie requesty
val client = KNETClient.builder()
.addInterceptor(KNETHeaderInterceptor.bearer(token))
.build()
// Dynamiczny token
val client = KNETClient.builder()
.addInterceptor(KNETHeaderInterceptor {
mapOf("Authorization" to "Bearer ${tokenProvider.getToken()}")
})
.build()
// Per request
val credentials = Base64.encodeToString(
"$username:$password".toByteArray(),
Base64.NO_WRAP
)
val response = client.get(url, mapOf(
"Authorization" to "Basic $credentials"
))
// Interceptor
val client = KNETClient.builder()
.addInterceptor(KNETHeaderInterceptor.basic(username, password))
.build()
val authInterceptor = KNETAuthInterceptor(
// Dostawca aktualnego tokena
tokenProvider = {
authRepository.getAccessToken()
},
// Logika refresh tokena
refreshToken = suspend {
val refreshToken = authRepository.getRefreshToken()
val response = client.post("$baseUrl/auth/refresh", mapOf(
"refresh_token" to refreshToken
))
val newTokens = response.jsonObject()
authRepository.saveTokens(
accessToken = newTokens["access_token"] as String,
refreshToken = newTokens["refresh_token"] as String
)
newTokens["access_token"] as String
},
// Kiedy refreshować
shouldRefresh = { response ->
response.statusCode == 401
},
// Max prób refresh
maxRefreshAttempts = 1
)
val client = KNETClient.builder()
.addInterceptor(authInterceptor)
.build()
class OAuth2Client(
private val clientId: String,
private val clientSecret: String,
private val tokenUrl: String
) {
private val client = KNETClient()
// Authorization Code Flow
suspend fun exchangeCode(code: String, redirectUri: String): TokenResponse {
val response = client.post(tokenUrl, mapOf(
"grant_type" to "authorization_code",
"code" to code,
"redirect_uri" to redirectUri,
"client_id" to clientId,
"client_secret" to clientSecret
))
return response.json<TokenResponse>()
}
// Client Credentials Flow
suspend fun getClientToken(): TokenResponse {
val response = client.post(tokenUrl, mapOf(
"grant_type" to "client_credentials",
"client_id" to clientId,
"client_secret" to clientSecret
))
return response.json<TokenResponse>()
}
// Refresh Token
suspend fun refresh(refreshToken: String): TokenResponse {
val response = client.post(tokenUrl, mapOf(
"grant_type" to "refresh_token",
"refresh_token" to refreshToken,
"client_id" to clientId
))
return response.json<TokenResponse>()
}
}
data class TokenResponse(
val access_token: String,
val refresh_token: String?,
val expires_in: Int,
val token_type: String
)
class SecureTokenStorage(context: Context) {
private val prefs = EncryptedSharedPreferences.create(
"auth_prefs",
MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC),
context,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)
fun saveTokens(accessToken: String, refreshToken: String) {
prefs.edit()
.putString("access_token", accessToken)
.putString("refresh_token", refreshToken)
.putLong("token_saved_at", System.currentTimeMillis())
.apply()
}
fun getAccessToken(): String? = prefs.getString("access_token", null)
fun getRefreshToken(): String? = prefs.getString("refresh_token", null)
fun isTokenExpired(expiresInSeconds: Int): Boolean {
val savedAt = prefs.getLong("token_saved_at", 0)
val expiresAt = savedAt + (expiresInSeconds * 1000)
return System.currentTimeMillis() >= expiresAt - 60_000 // 1 min buffer
}
fun clearTokens() {
prefs.edit().clear().apply()
}
}
// W header
val client = KNETClient.builder()
.addInterceptor(KNETHeaderInterceptor(mapOf(
"X-API-Key" to apiKey
)))
.build()
// W query
val response = client.get(
url = "https://api.example.com/data",
query = mapOf("api_key" to apiKey)
)
class MultiTenantAuthInterceptor(
private val tenantResolver: () -> String,
private val tokenProvider: (String) -> String?
) : KNETInterceptor {
override suspend fun intercept(
request: KNETRequest,
chain: suspend (KNETRequest) -> KNETResponse
): KNETResponse {
val tenant = tenantResolver()
val token = tokenProvider(tenant)
val authRequest = if (token != null) {
request.copy(
headers = request.headers + mapOf(
"Authorization" to "Bearer $token",
"X-Tenant-ID" to tenant
)
)
} else {
request
}
return chain(authRequest)
}
}
class AuthRepository(
private val client: KNETClient,
private val storage: SecureTokenStorage
) {
private val _authState = MutableStateFlow<AuthState>(AuthState.Unknown)
val authState: StateFlow<AuthState> = _authState
suspend fun login(email: String, password: String): Result<User> {
return try {
val response = client.post("$baseUrl/auth/login", mapOf(
"email" to email,
"password" to password
))
val data = response.jsonObject()
storage.saveTokens(
accessToken = data["access_token"] as String,
refreshToken = data["refresh_token"] as String
)
val user = (data["user"] as Map<*, *>).let { User.fromMap(it) }
_authState.value = AuthState.Authenticated(user)
Result.success(user)
} catch (e: Exception) {
Result.failure(e)
}
}
suspend fun logout() {
try {
client.post("$baseUrl/auth/logout", emptyMap<String, Any>())
} finally {
storage.clearTokens()
_authState.value = AuthState.NotAuthenticated
}
}
fun getAccessToken() = storage.getAccessToken()
fun getRefreshToken() = storage.getRefreshToken()
}
sealed class AuthState {
object Unknown : AuthState()
object NotAuthenticated : AuthState()
data class Authenticated(val user: User) : AuthState()
}