বায়োমেট্রিক বা ফেসআইডি / টাচআইডি অথেনটিকেশন – iOS

আইওএস এ বায়োমেট্রিক অথেনটিকেশনের জন্য LocalAuthentication রয়েছে। যা ব্যবহার করে খুব সহজেই ফেস আইডি অথবা টাচ আইডি অথেনটিকেশন ইমপ্লেমেন্ট করা যায়। যার মাধ্যমে ইউজাররা খুব সহজে বায়োমেট্রিক লগিন করতে পারে।

বায়োমেট্রিক দিয়ে প্রথমে যাচাই হয় যে লগিন করতে চায়, সে আপনি কিনা। তারপর লগিন করে। এই টিউটোরিয়ালের জন্য আমরা একটা ফেইক লগিন ইমপ্লিমেন্ট করব। যা বুঝতে পারলে রিয়েল প্রজেক্টে সহজে বায়োমেট্রিক লগিন ইমপ্লিমেন্ট করা যাবে। আর এর জন্য আমরা একটা AuthManager.swift ফাইল তৈরি করে নিবঃ

import SwiftUI
import LocalAuthentication
 
class AuthManager: ObservableObject {
    @Published var username: String = "admin"
    @Published var password: String = "123456"
     
    @Published var isAuthenticated = false
    @Published var errorMessage: String?
     
    func loginWithFaceID() async{
        let context = LAContext()
        let reason = "Log into your account"
        do {
            // Check if Face ID is available
            let success = try await context.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: reason)
            if success{
                // If user authenticated using faceID, login.
                await fakeLogin()
            }
        } catch {
            // No biometric authentication available
            DispatchQueue.main.async{
                self.errorMessage = error.localizedDescription
            }
        }
    }
    func fakeLogin() async{
        // for now, we will check the credential from hardcoded data.
        if username.lowercased() == "admin" && password == "123456" {
            DispatchQueue.main.async{
                self.isAuthenticated = true
            }
        } else {
            DispatchQueue.main.async{
                self.errorMessage = "Wrong Credentials"
            }
        }
    }

এখানে আমরা প্রথমে LocalAuthentication ব্যবহার করে দেখেছি বায়োমেট্রিক ফিচার রয়েছে কিনা ডিভাইসে। যদি থাকে, তারপর বায়োমেট্রিক অথেনটিকেশনের জন্য চেষ্টা করেছি। এরপর বায়োমেট্রিক লগিন সম্পর্ণ হলে আমরা একটা ফেইক লগিন ফাংশন কল করে অথেনটিকেশন ডেমো দেখিয়েছি।

ইউজার ইন্টারফেস, ContentView.swift:

import SwiftUI
 
struct ContentView: View {
    @StateObject private var authManager = AuthManager()
     
    var body: some View {
        VStack {
            if authManager.isAuthenticated {
                Text("Welcome, you are authenticated!")
                    .font(.title)
                Button(action: {
                    authManager.logout()
                }, label: {
                    Label("Logout", systemImage: "arrow.backward.circle")
                })
                .font(.title)
                 
            } else {
                Button(action: {
                    Task{
                        await   authManager.loginWithFaceID()
                    }
                }, label: {
                    Label("Login with Face ID", systemImage: "faceid")
                })
                .font(.title)
                 
                if let errorMessage = authManager.errorMessage {
                    Text(errorMessage)
                        .foregroundColor(.red)
                        .padding()
                }
            }
        }
        .padding()
    }
}
 
 
#Preview {
    ContentView()
}

খুবি সিম্পল একটা ইন্টারফেস। প্রথমে আমরা একটা বাটন দেখিয়েছি। যদি লগিন সাকসেসফুল হয়, তাহলে একটা ওয়েলকাম টেক্সট দেখাবে। এই তো।

লগইনের পূর্বে:

লগিনের পরেঃ

আমরা সিমুলেটরে ফেস আইডি সিমুলেট করতে পারি। তার জন্যঃ

১ – সিমুলেটরের মেন্যু থেকে Features > Face ID > Enrolled এ ক্লিক করে প্রথমে ফেসআইডি এনাবল করতে হবে।

২ – অ্যাপের login with Face ID বাটনে ক্লিক করব।

৩ – এরপর আবার মেন্যু থেকে Features > Face ID > Matching Face এ ক্লিক করলে লগিন হবে।

ইম্প্রুভমেন্টসঃ ফেসআইডি বা বায়োমেট্রিক ব্যবহার করতে চাইলে আমাদের ইউজারনেম এবং পাসওয়ার্ড লোকালি স্টোর করে রাখতে হবে। সিকিউর ভাবে এই ক্রেডেনশিয়াল স্টোর করার মাধ্যম হচ্ছে Keychain।

কিচেইনে ক্রেডেনশিয়াল স্টোর করা

iOS কিচেইন এ ক্রেডেনশিয়াল স্টোর এবং পুনরুদ্ধার এভাবে করতে পারিঃ

import Security
 
func saveToKeychain(service: String, account: String, data: Data) -> Bool {
    func saveToKeychain(service: String, account: String, data: Data) -> Bool {
        let query = [
            kSecClass: kSecClassGenericPassword,
            kSecAttrService: service,
            kSecAttrAccount: account,
            kSecValueData: data
        ] as CFDictionary
         
        // Delete any existing items
        SecItemDelete(query)
         
        // Add new keychain item
        let status = SecItemAdd(query, nil)
        return status == errSecSuccess
    }
     
     
    func retrieveFromKeychain(service: String, account: String) -> Data? {
        let query = [
            kSecClass: kSecClassGenericPassword,
            kSecAttrService: service,
            kSecAttrAccount: account,
            kSecReturnData: true,
            kSecMatchLimit: kSecMatchLimitOne
        ] as CFDictionary
         
        var result: AnyObject?
        let status = SecItemCopyMatching(query, &result)
         
        if status == errSecSuccess {
            return result as? Data
        } else {
            return nil
        }
    }

এবার আমরা বায়োমেট্রিক ম্যানেজার এভাবে লিখতে পারিঃ

import SwiftUI
import LocalAuthentication
 
import Security
 
class AuthManager: ObservableObject {
    @AppStorage("username") var username = ""
    @Published var password: String = ""
    @Published var service = "com.yourapp.service"
     
    @Published var isAuthenticated = false
    @Published var errorMessage: String?
     
    func biometricLogin() async{
        let context = LAContext()
        let reason = "Log into your account"
        do {
            // Check if Face ID is available
            let success = try await context.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: reason)
            if success{
                self.retrieveCredentialsAndLogin()
            } else {
                DispatchQueue.main.async{
                    self.errorMessage =  "Try to login using credential first."
                }
            }
        } catch {
            // No biometric authentication available
            DispatchQueue.main.async{
                self.errorMessage = error.localizedDescription
            }
        }
    }
 
    func credentialLogin() async{
        // for now, we will check the credential from hardcoded data.
        if username.lowercased() == "admin" && password == "123456" {
            DispatchQueue.main.async{
                // store credential to keychain
                _ = self.saveToKeychain(service: self.service, account: self.username, data: Data(self.password.utf8))
                self.isAuthenticated = true
                self.errorMessage = ""
            }
        } else {
            DispatchQueue.main.async{
                self.errorMessage = "Wrong Credentials."
            }
        }
    }
     
 
    private func retrieveCredentialsAndLogin() {
         
        if let passwordData = retrieveFromKeychain(service: service, account: username),
           let retrievePassword = String(data: passwordData, encoding: .utf8) {
            if username.lowercased() == "admin" && retrievePassword == "123456" {
                DispatchQueue.main.async{
                    self.isAuthenticated = true
                    self.errorMessage = ""
                }
            }else {
                DispatchQueue.main.async{
                    self.errorMessage = "Wrong Credentials."
                }
            }
        } else {
            self.errorMessage = "Failed to retrieve credentials."
        }
    }
     
     
    func saveToKeychain(service: String, account: String, data: Data) -> Bool {
        let query = [
            kSecClass: kSecClassGenericPassword,
            kSecAttrService: service,
            kSecAttrAccount: account,
            kSecValueData: data
        ] as CFDictionary
         
        // Delete any existing items
        SecItemDelete(query)
         
        // Add new keychain item
        let status = SecItemAdd(query, nil)
        return status == errSecSuccess
    }
     
     
    func retrieveFromKeychain(service: String, account: String) -> Data? {
        let query = [
            kSecClass: kSecClassGenericPassword,
            kSecAttrService: service,
            kSecAttrAccount: account,
            kSecReturnData: true,
            kSecMatchLimit: kSecMatchLimitOne
        ] as CFDictionary
         
        var result: AnyObject?
        let status = SecItemCopyMatching(query, &result)
         
        if status == errSecSuccess {
            return result as? Data
        } else {
            return nil
        }
    }
     
    func logout(){
        isAuthenticated = false
    }
}

যেহেতু প্রথমবার ইউজার থেকে ইউজারনেম এবং পাসওয়ার্ড ইনপুট নিতে হবে। তাই নতুন ContentView.swift ফাইলঃ

import SwiftUI
 
struct ContentView: View {
    @StateObject private var authManager = AuthManager()
     
    var body: some View {
        VStack {
            if authManager.isAuthenticated {
                Text("Welcome, you are authenticated!")
                    .font(.title)
                Button(action: {
                    authManager.logout()
                }, label: {
                    Label("Logout", systemImage: "arrow.backward.circle")
                })
                .font(.title)
                 
            } else {
                VStack(spacing: 40){
                    TextField("Username", text: $authManager.username)
                    SecureField("Password", text: $authManager.password)
                    Button("Login with Credential") {
                        Task{
                            await authManager.credentialLogin()
                        }
                    }
                    Button("Login with FaceID / TouchID") {
                        Task{
                            await authManager.biometricLogin()
                        }
                    }
                    if let errorMessage = authManager.errorMessage {
                        Text(errorMessage)
                            .foregroundColor(.red)
                            .padding()
                    }
                }
                .textFieldStyle(.roundedBorder)
                .font(.title)
            }
        }
        .padding()
    }
}

#Preview {
    ContentView()
}

নতুন ভিউঃ

এখন ইজার প্রথমবার ক্রেডেনশিয়াল ব্যবহার করে লগিন করতে পারবে। আমরা এই ক্রেডেনশিয়াল কিচেইনে স্টোর করে রাখব। তাই পরবর্তী ইউজার বায়োমেট্রিক লগিন করতে পারবে।

Leave a Reply