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

  1. 📌 Introduction
  2. 🛠 Project Setup
  3. 💾 Storing Data with UserDefaults
  4. 🔐 Storing Secure Data in Keychain
  5. 🎨 Creating the UI
  6. 📲 Implementing Face ID Authentication
  7. 🚀 Running & Testing the App
  8. 🔗 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

  1. Open Xcode and select “Create a new Xcode project”
  2. Choose App, then click Next
  3. Set the following: • Product Name: SecureNotes • Interface: SwiftUI • Language: Swift
  4. Click Next, choose a location, and click Create.

2️⃣ Enable Face ID (for later)

  1. In Xcode, go to Signing & Capabilities
  2. Click + Capability → Keychain Sharing
  3. 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

  1. 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)
    }
}
  1. 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! 🚀