Build a Secure Notes iPhone App
🔐 In this tutorial, you’ll learn how to securely store small amounts of data using UserDefaults and Keychain in Swift. We’ll build a Secure Notes app where users can create simple text notes, with the option to store them securely using Face ID / Touch ID authentication.
⸻
📖 Table of Contents
- 📌 Introduction
- 🛠 Project Setup
- 💾 Storing Data with UserDefaults
- 🔐 Storing Secure Data in Keychain
- 🎨 Creating the UI
- 📲 Implementing Face ID Authentication
- 🚀 Running & Testing the App
- 🔗 Conclusion & Next Steps
⸻
📌 Introduction
iOS provides two main ways to store small amounts of data securely:
• UserDefaults: Ideal for storing non-sensitive data like user preferences.
• Keychain: Used for storing sensitive data like passwords, API keys, or private notes.
In this tutorial, we’ll build Secure Notes, a minimalist note-taking app where:
✅ Normal notes are stored in UserDefaults*
✅ Secure notes are stored in **Keychain, requiring Face ID / Touch ID to access
⸻
🛠 Project Setup
1️⃣ Create a New Xcode Project
- Open Xcode and select “Create a new Xcode project”
- Choose App, then click Next
- Set the following: • Product Name: SecureNotes • Interface: SwiftUI • Language: Swift
- Click Next, choose a location, and click Create.
2️⃣ Enable Face ID (for later)
- In Xcode, go to Signing & Capabilities
- Click + Capability → Keychain Sharing
- Click + Capability → Face ID
⸻
💾 Storing Data with UserDefaults
We’ll first store simple notes using UserDefaults.
1️⃣ Create a Model for Notes
Create a new Swift file: Note.swift
import Foundation
struct Note: Identifiable, Codable {
var id: UUID = UUID()
var text: String
var isSecure: Bool
}
2️⃣ Create a UserDefaults Helper
Create a new Swift file: UserDefaultsManager.swift
import Foundation
class UserDefaultsManager {
private let key = "savedNotes"
func saveNotes(_ notes: [Note]) {
if let encoded = try? JSONEncoder().encode(notes) {
UserDefaults.standard.set(encoded, forKey: key)
}
}
func loadNotes() -> [Note] {
if let savedData = UserDefaults.standard.data(forKey: key),
let decoded = try? JSONDecoder().decode([Note].self, from: savedData) {
return decoded
}
return []
}
}
⸻
🔐 Storing Secure Data in Keychain
Since Keychain doesn’t support Swift’s Codable directly, we’ll write a wrapper.
1️⃣ Create a Keychain Helper
Create a new Swift file: KeychainManager.swift
import Security
import Foundation
class KeychainManager {
static let shared = KeychainManager()
func save(_ note: Note) {
let key = note.id.uuidString
guard let data = try? JSONEncoder().encode(note) else { return }
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: key,
kSecValueData as String: data
]
SecItemAdd(query as CFDictionary, nil)
}
func load(id: UUID) -> Note? {
let key = id.uuidString
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: key,
kSecReturnData as String: true,
kSecMatchLimit as String: kSecMatchLimitOne
]
var dataTypeRef: AnyObject?
if SecItemCopyMatching(query as CFDictionary, &dataTypeRef) == errSecSuccess,
let data = dataTypeRef as? Data,
let note = try? JSONDecoder().decode(Note.self, from: data) {
return note
}
return nil
}
}
⸻
🎨 Creating the UI
Create a simple SwiftUI interface.
1️⃣ Create the Notes List
Modify ContentView.swift
import SwiftUI
struct ContentView: View {
@State private var notes: [Note] = UserDefaultsManager().loadNotes()
@State private var newText: String = ""
var body: some View {
NavigationView {
VStack {
TextField("Enter your note", text: $newText)
.textFieldStyle(RoundedBorderTextFieldStyle())
.padding()
Button("Save Note") {
let note = Note(text: newText, isSecure: false)
notes.append(note)
UserDefaultsManager().saveNotes(notes)
newText = ""
}
.buttonStyle(.borderedProminent)
List(notes) { note in
Text(note.text)
}
}
.navigationTitle("Secure Notes")
}
}
}
⸻
📲 Implementing Face ID Authentication
- Add LocalAuthentication to ContentView.swift
import LocalAuthentication
func authenticateUser(completion: @escaping (Bool) -> Void) {
let context = LAContext()
var error: NSError?
if context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error) {
context.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: "Access Secure Notes") { success, _ in
DispatchQueue.main.async {
completion(success)
}
}
} else {
completion(false)
}
}
- Use Face ID before loading secure notes:
Button("Load Secure Note") {
authenticateUser { success in
if success {
if let secureNote = KeychainManager.shared.load(id: someUUID) {
print("Secure Note:", secureNote.text)
}
}
}
}
⸻
🚀 Running & Testing the App
✅ Run on a real device to test Face ID / Touch ID.
✅ Save regular notes → Restart the app → Check if they persist.
✅ Save a secure note → Restart → Authenticate with Face ID → Retrieve it.
⸻
🔗 Conclusion & Next Steps
🎉 Congratulations! You’ve built Secure Notes, learning:
✅ How to use UserDefaults for non-sensitive data
✅ How to use Keychain for secure storage
✅ How to authenticate users with Face ID / Touch ID
📌 Next Steps:
• Add a delete note feature.
• Support multiple secure notes.
• Implement Dark Mode UI improvements.
🚀 Happy Coding! 🚀