0Pricing
Swift Academy · Lesson

Storing Secrets in the Keychain

Save and retrieve credentials securely.

Storing Secrets in the Keychain is a free Swift Academy lesson on CoddyKit — lesson 1 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the Swift Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.

Why the Keychain

Passwords, tokens, and keys must never sit in UserDefaults or plain files — those are easy to read. The Keychain is an encrypted, OS-managed database for small secrets, protected by hardware and the user's passcode. It is the only correct place for credentials.

import Security
// Keychain stores secrets encrypted at rest,
// survives app updates, and gates access by policy.

Items Are Dictionaries

The Keychain Services API is C-based: you describe an item with a [String: Any] query dictionary using kSec... constant keys. The same dictionary shape is reused for add, search, update, and delete.

import Security
let query: [String: Any] = [
    kSecClass as String: kSecClassGenericPassword,
    kSecAttrAccount as String: "user@example.com",
    kSecAttrService as String: "com.example.app"
]
_ = query

Item Classes

The kSecClass key picks the item type. kSecClassGenericPassword covers app tokens and secrets; kSecClassInternetPassword stores server credentials with host/protocol attributes. Most app secrets use generic password.

import Security
// kSecClassGenericPassword   -> tokens, API keys
// kSecClassInternetPassword  -> server logins
// kSecClassKey / Certificate -> crypto material
let cls = kSecClassGenericPassword
_ = cls

Adding an Item

SecItemAdd inserts a new item. You include the data under kSecValueData as Data, plus attributes that identify it. It returns an OSStatuserrSecSuccess means it worked.

import Security
func save(_ token: String, account: String) -> Bool {
    let query: [String: Any] = [
        kSecClass as String: kSecClassGenericPassword,
        kSecAttrAccount as String: account,
        kSecValueData as String: Data(token.utf8)
    ]
    return SecItemAdd(query as CFDictionary, nil)
        == errSecSuccess
}

Handling Duplicates

Adding an item whose identifying attributes already exist returns errSecDuplicateItem. A robust save tries SecItemAdd, and on duplicate falls back to SecItemUpdate — an upsert pattern.

import Security
func upsert(_ data: Data, account: String) -> Bool {
    let base: [String: Any] = [
        kSecClass as String: kSecClassGenericPassword,
        kSecAttrAccount as String: account]
    var add = base
    add[kSecValueData as String] = data
    let status = SecItemAdd(add as CFDictionary, nil)
    if status == errSecDuplicateItem {
        return SecItemUpdate(base as CFDictionary,
            [kSecValueData as String: data] as CFDictionary)
            == errSecSuccess
    }
    return status == errSecSuccess
}

Reading an Item Back

SecItemCopyMatching searches. To get the secret bytes you must set kSecReturnData to true and kSecMatchLimit to kSecMatchLimitOne. The result comes back through an out-parameter as CFTypeRef.

import Security
func load(account: String) -> Data? {
    let query: [String: Any] = [
        kSecClass as String: kSecClassGenericPassword,
        kSecAttrAccount as String: account,
        kSecReturnData as String: true,
        kSecMatchLimit as String: kSecMatchLimitOne]
    var result: CFTypeRef?
    let status = SecItemCopyMatching(
        query as CFDictionary, &result)
    guard status == errSecSuccess else { return nil }
    return result as? Data
}

Updating an Item

SecItemUpdate takes two dictionaries: a query that finds the item and an attributes-to-update dictionary. Only the attributes you list change; everything else is preserved.

import Security
func update(_ newData: Data, account: String) -> Bool {
    let query: [String: Any] = [
        kSecClass as String: kSecClassGenericPassword,
        kSecAttrAccount as String: account]
    let attrs: [String: Any] = [
        kSecValueData as String: newData]
    return SecItemUpdate(query as CFDictionary,
        attrs as CFDictionary) == errSecSuccess
}

Deleting an Item

SecItemDelete removes matching items. Deleting something that does not exist returns errSecItemNotFound, which you can treat as success when clearing credentials on logout.

import Security
func delete(account: String) -> Bool {
    let query: [String: Any] = [
        kSecClass as String: kSecClassGenericPassword,
        kSecAttrAccount as String: account]
    let status = SecItemDelete(query as CFDictionary)
    return status == errSecSuccess
        || status == errSecItemNotFound
}

Decoding OSStatus

Errors are integer OSStatus codes. SecCopyErrorMessageString turns one into a human-readable description, which is invaluable when debugging why a save or read failed.

import Security
func describe(_ status: OSStatus) -> String {
    return SecCopyErrorMessageString(status, nil)
        as String? ?? "OSStatus \(status)"
}

Identifying Items Uniquely

Items are matched by their combination of attributes — typically kSecAttrService plus kSecAttrAccount for generic passwords. Choose a stable, app-specific service string so different secrets never collide.

import Security
// Uniqueness for generic passwords usually comes from:
//   service (your bundle id) + account (the username)
let service = "com.example.app.auth"
let account = "current-user"
_ = (service, account)

A Small Wrapper

Because the raw API is verbose, teams wrap it in a tiny type exposing save, read, and delete. This keeps the kSec ceremony in one place and the call sites clean.

import Security
struct TokenStore {
    let service = "com.example.app.auth"
    func read(_ account: String) -> Data? {
        let q: [String: Any] = [
            kSecClass as String: kSecClassGenericPassword,
            kSecAttrService as String: service,
            kSecAttrAccount as String: account,
            kSecReturnData as String: true,
            kSecMatchLimit as String: kSecMatchLimitOne]
        var out: CFTypeRef?
        return SecItemCopyMatching(q as CFDictionary, &out)
            == errSecSuccess ? out as? Data : nil
    }
}

Quick Check

Recall the correct way to retrieve a stored secret's bytes.

Recap

You learned Keychain CRUD:

  • Store credentials in the encrypted Keychain, never in UserDefaults or files.
  • Describe items with kSec query dictionaries; pick a class like kSecClassGenericPassword.
  • SecItemAdd / SecItemCopyMatching / SecItemUpdate / SecItemDelete cover the lifecycle; handle errSecDuplicateItem with an upsert.
  • Identify items by service + account, and decode OSStatus for debugging.

Frequently asked questions

Is the “Storing Secrets in the Keychain” lesson free?

Yes — the full text of “Storing Secrets in the Keychain” is free to read here on the web, and the Swift Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the Swift Academy course, upgrade to CoddyKit PRO.

What will I learn in “Storing Secrets in the Keychain”?

Save and retrieve credentials securely. You practise Swift Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.

Do I need any experience to start Swift Academy?

No prior experience is required. Swift Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 1 of 4, so you can start here or from the beginning and move at your own pace.

How long does the “Storing Secrets in the Keychain” lesson take?

Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.

Can I write and run code in this Swift Academy lesson?

Yes. Every Swift Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.

All lessons in this course

  1. Storing Secrets in the Keychain
  2. Keychain Access Control
  3. Biometric Authentication
  4. Data Protection and Encryption
← Back to Swift Academy