আইওএস এ বায়োমেট্রিক অথেনটিকেশনের জন্য 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()
}
নতুন ভিউঃ
এখন ইজার প্রথমবার ক্রেডেনশিয়াল ব্যবহার করে লগিন করতে পারবে। আমরা এই ক্রেডেনশিয়াল কিচেইনে স্টোর করে রাখব। তাই পরবর্তী ইউজার বায়োমেট্রিক লগিন করতে পারবে।