Skip to content

Kimlik Doğrulama Kalıpları

Genel Bakış

Mobil uygulamalarda güvenli kimlik doğrulama, kullanıcı verilerini korumak ve uygulamanızın güvenilirliğini sağlamak için kritik öneme sahiptir. Bu dokümantasyon modern kimlik doğrulama kalıplarını ve en iyi uygulamaları kapsar.

OAuth 2.0 ve OpenID Connect

iOS Implementation

swift
import AuthenticationServices

class OAuthManager: NSObject, ASWebAuthenticationPresentationContextProviding {
    func authenticate(completion: @escaping (Result<AuthToken, Error>) -> Void) {
        let authURL = URL(string: "https://accounts.google.com/oauth/authorize?client_id=\(clientId)&redirect_uri=\(redirectUri)&response_type=code&scope=openid profile email")!
        
        let session = ASWebAuthenticationSession(url: authURL, callbackURLScheme: "myapp") { callbackURL, error in
            if let error = error {
                completion(.failure(error))
                return
            }
            
            guard let callbackURL = callbackURL,
                  let code = URLComponents(url: callbackURL, resolvingAgainstBaseURL: false)?
                    .queryItems?.first(where: { $0.name == "code" })?.value else {
                completion(.failure(AuthError.invalidResponse))
                return
            }
            
            self.exchangeCodeForToken(code: code, completion: completion)
        }
        
        session.presentationContextProvider = self
        session.start()
    }
    
    private func exchangeCodeForToken(code: String, completion: @escaping (Result<AuthToken, Error>) -> Void) {
        // Token exchange implementation
        let tokenRequest = TokenRequest(
            clientId: clientId,
            clientSecret: clientSecret,
            code: code,
            redirectUri: redirectUri
        )
        
        APIService.exchangeToken(tokenRequest) { result in
            DispatchQueue.main.async {
                completion(result)
            }
        }
    }
    
    func presentationAnchor(for session: ASWebAuthenticationSession) -> ASPresentationAnchor {
        return UIApplication.shared.windows.first { $0.isKeyWindow } ?? ASPresentationAnchor()
    }
}

Android Implementation

kotlin
import androidx.browser.customtabs.CustomTabsIntent
import net.openid.appauth.*

class OAuthManager(private val context: Context) {
    private val authService = AuthorizationService(context)
    
    fun authenticate(callback: (AuthToken?, Exception?) -> Unit) {
        val serviceConfig = AuthorizationServiceConfiguration(
            Uri.parse("https://accounts.google.com/oauth/authorize"),
            Uri.parse("https://oauth2.googleapis.com/token")
        )
        
        val authRequest = AuthorizationRequest.Builder(
            serviceConfig,
            CLIENT_ID,
            ResponseTypeValues.CODE,
            Uri.parse(REDIRECT_URI)
        )
        .setScope("openid profile email")
        .build()
        
        val authIntent = authService.getAuthorizationRequestIntent(authRequest)
        
        // Activity result'ı handle et
        (context as Activity).startActivityForResult(authIntent, AUTH_REQUEST_CODE)
    }
    
    fun handleAuthorizationResponse(
        intent: Intent,
        callback: (AuthToken?, Exception?) -> Unit
    ) {
        val response = AuthorizationResponse.fromIntent(intent)
        val exception = AuthorizationException.fromIntent(intent)
        
        if (response != null) {
            exchangeCodeForToken(response, callback)
        } else {
            callback(null, exception)
        }
    }
    
    private fun exchangeCodeForToken(
        response: AuthorizationResponse,
        callback: (AuthToken?, Exception?) -> Unit
    ) {
        val tokenRequest = response.createTokenExchangeRequest()
        
        authService.performTokenRequest(tokenRequest) { tokenResponse, exception ->
            if (tokenResponse != null) {
                val authToken = AuthToken(
                    accessToken = tokenResponse.accessToken!!,
                    refreshToken = tokenResponse.refreshToken,
                    expiresAt = tokenResponse.accessTokenExpirationTime
                )
                callback(authToken, null)
            } else {
                callback(null, exception)
            }
        }
    }
}

JWT Token Management

Token Storage ve Refresh

swift
import Security

class TokenManager {
    private let keychain = KeychainWrapper.standard
    
    func storeToken(_ token: AuthToken) {
        do {
            let tokenData = try JSONEncoder().encode(token)
            keychain.set(tokenData, forKey: "auth_token")
        } catch {
            print("Failed to store token: \(error)")
        }
    }
    
    func getToken() -> AuthToken? {
        guard let tokenData = keychain.data(forKey: "auth_token") else { return nil }
        
        do {
            return try JSONDecoder().decode(AuthToken.self, from: tokenData)
        } catch {
            print("Failed to decode token: \(error)")
            return nil
        }
    }
    
    func refreshTokenIfNeeded() async -> AuthToken? {
        guard let currentToken = getToken() else { return nil }
        
        // Token'ın süresi dolmuş mu kontrol et
        if currentToken.isExpired() {
            return await refreshToken(currentToken.refreshToken)
        }
        
        return currentToken
    }
    
    private func refreshToken(_ refreshToken: String?) async -> AuthToken? {
        guard let refreshToken = refreshToken else { return nil }
        
        do {
            let newToken = try await APIService.refreshToken(refreshToken)
            storeToken(newToken)
            return newToken
        } catch {
            print("Token refresh failed: \(error)")
            // Refresh token geçersizse kullanıcıyı yeniden login'e yönlendir
            clearToken()
            return nil
        }
    }
    
    func clearToken() {
        keychain.removeObject(forKey: "auth_token")
    }
}

Biometric Authentication

iOS Face ID / Touch ID

swift
import LocalAuthentication

class BiometricAuthManager {
    private let context = LAContext()
    
    func authenticateWithBiometrics(completion: @escaping (Result<Bool, Error>) -> Void) {
        var error: NSError?
        
        if context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error) {
            let reason = "Uygulamaya erişim için kimlik doğrulaması gerekiyor"
            
            context.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: reason) { success, error in
                DispatchQueue.main.async {
                    if success {
                        completion(.success(true))
                    } else {
                        completion(.failure(error ?? BiometricError.unknown))
                    }
                }
            }
        } else {
            completion(.failure(error ?? BiometricError.notAvailable))
        }
    }
    
    func getBiometricType() -> LABiometryType {
        context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: nil)
        return context.biometryType
    }
}

enum BiometricError: Error {
    case notAvailable
    case unknown
    
    var localizedDescription: String {
        switch self {
        case .notAvailable:
            return "Biyometrik kimlik doğrulama mevcut değil"
        case .unknown:
            return "Bilinmeyen hata oluştu"
        }
    }
}

Android Fingerprint / Face Recognition

kotlin
import androidx.biometric.BiometricPrompt
import androidx.fragment.app.FragmentActivity

class BiometricAuthManager(private val activity: FragmentActivity) {
    
    fun authenticateWithBiometrics(
        onSuccess: () -> Unit,
        onError: (String) -> Unit
    ) {
        val executor = ContextCompat.getMainExecutor(activity)
        
        val biometricPrompt = BiometricPrompt(activity, executor,
            object : BiometricPrompt.AuthenticationCallback() {
                override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
                    super.onAuthenticationError(errorCode, errString)
                    onError(errString.toString())
                }
                
                override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
                    super.onAuthenticationSucceeded(result)
                    onSuccess()
                }
                
                override fun onAuthenticationFailed() {
                    super.onAuthenticationFailed()
                    onError("Kimlik doğrulama başarısız")
                }
            })
        
        val promptInfo = BiometricPrompt.PromptInfo.Builder()
            .setTitle("Biyometrik Kimlik Doğrulama")
            .setSubtitle("Parmak izinizi veya yüzünüzü kullanarak giriş yapın")
            .setNegativeButtonText("İptal")
            .build()
        
        biometricPrompt.authenticate(promptInfo)
    }
    
    fun isBiometricAvailable(): Boolean {
        return BiometricManager.from(activity).canAuthenticate(
            BiometricManager.Authenticators.BIOMETRIC_WEAK
        ) == BiometricManager.BIOMETRIC_SUCCESS
    }
}

Multi-Factor Authentication (MFA)

TOTP Implementation

swift
import CryptoKit

class TOTPManager {
    private let secretKey: Data
    
    init(secretKey: String) {
        self.secretKey = Data(base32Encoded: secretKey) ?? Data()
    }
    
    func generateTOTP(timeStep: TimeInterval = 30) -> String {
        let counter = UInt64(Date().timeIntervalSince1970 / timeStep)
        return generateHOTP(counter: counter)
    }
    
    private func generateHOTP(counter: UInt64) -> String {
        var counterBytes = withUnsafeBytes(of: counter.bigEndian) { Data($0) }
        
        let hmac = HMAC<Insecure.SHA1>.authenticationCode(for: counterBytes, using: SymmetricKey(data: secretKey))
        let hmacData = Data(hmac)
        
        let offset = Int(hmacData[hmacData.count - 1] & 0x0f)
        let truncatedHash = hmacData.subdata(in: offset..<offset + 4)
        
        let code = truncatedHash.withUnsafeBytes { bytes in
            let value = bytes.load(as: UInt32.self).bigEndian
            return (value & 0x7fffffff) % 1000000
        }
        
        return String(format: "%06d", code)
    }
    
    func verifyTOTP(_ userCode: String, tolerance: Int = 1) -> Bool {
        let currentTime = Date().timeIntervalSince1970
        let timeStep: TimeInterval = 30
        
        for i in -tolerance...tolerance {
            let testTime = currentTime + Double(i) * timeStep
            let testCounter = UInt64(testTime / timeStep)
            let expectedCode = generateHOTP(counter: testCounter)
            
            if userCode == expectedCode {
                return true
            }
        }
        
        return false
    }
}

Session Management

Secure Session Handling

kotlin
class SessionManager(private val context: Context) {
    private val sharedPrefs = context.getSharedPreferences("secure_session", Context.MODE_PRIVATE)
    private val encryptedPrefs = EncryptedSharedPreferences.create(
        "encrypted_session",
        MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC),
        context,
        EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
        EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
    )
    
    fun createSession(user: User, token: AuthToken) {
        val sessionData = SessionData(
            userId = user.id,
            token = token,
            createdAt = System.currentTimeMillis(),
            expiresAt = token.expiresAt
        )
        
        encryptedPrefs.edit()
            .putString("session_data", sessionData.toJson())
            .apply()
        
        // Session timeout ayarla
        scheduleSessionTimeout(token.expiresAt)
    }
    
    fun getSession(): SessionData? {
        val sessionJson = encryptedPrefs.getString("session_data", null)
        return sessionJson?.let { SessionData.fromJson(it) }
    }
    
    fun isSessionValid(): Boolean {
        val session = getSession() ?: return false
        return System.currentTimeMillis() < session.expiresAt
    }
    
    fun clearSession() {
        encryptedPrefs.edit().clear().apply()
        cancelSessionTimeout()
    }
    
    private fun scheduleSessionTimeout(expiresAt: Long) {
        val workRequest = OneTimeWorkRequestBuilder<SessionTimeoutWorker>()
            .setInitialDelay(expiresAt - System.currentTimeMillis(), TimeUnit.MILLISECONDS)
            .build()
        
        WorkManager.getInstance(context).enqueue(workRequest)
    }
}

Bu kimlik doğrulama kalıpları ile güvenli, kullanıcı dostu ve modern mobil uygulamalar geliştirebilirsiniz.

Eren Demir tarafından oluşturulmuştur.