Skip to main content
NOT Using macOS KeychainDespite its name, this service does not use the macOS Keychain. Credentials are stored in plain text in UserDefaults without encryption. This is a deliberate design choice for a personal single-user tool, prioritizing simplicity over maximum security.Security implications:
  • Credentials are stored unencrypted
  • Accessible to other processes with UserDefaults access
  • Included in Time Machine backups
  • Visible in UserDefaults plist files
For production applications handling sensitive credentials, consider using the system Keychain (Security framework) instead.

Overview

KeychainService provides persistent storage for R2 credentials. Despite its name, this implementation uses UserDefaults rather than the system Keychain, which is appropriate for a personal single-user tool where credentials are stored on the user’s own machine.
Located at: Fiaxe/Services/KeychainService.swift:6

Type Definition

Implemented as an enum with static methods (no instances).

Methods

saveAll()

Saves an array of R2 credentials to persistent storage.
[R2Credentials]
required
Array of R2 credentials to save. Replaces any previously saved credentials.
Throws: Encoding errors if the credentials cannot be serialized to JSON.

Implementation Details

The method:
  1. Encodes the credentials array to JSON using JSONEncoder
  2. Stores the JSON data in UserDefaults under the key "fiaxe.r2credentials"
  3. Automatically synchronizes to disk via UserDefaults

loadAll()

Retrieves all stored R2 credentials from persistent storage.
Returns: Array of stored R2 credentials. Returns an empty array if no credentials are stored. Throws: Decoding errors if the stored data cannot be deserialized.

Implementation Details

The method handles migration from older versions:
  1. Attempts to load data from UserDefaults
  2. If no data exists, returns an empty array
  3. First tries to decode as an array of credentials (current format)
  4. Falls back to decoding a single credential object (legacy format)
  5. Automatically wraps legacy single credential in an array
  6. Returns empty array if decoding fails

deleteAll()

Removes all stored credentials from persistent storage.
Throws: No errors are thrown in the current implementation.

Implementation Details

Simply removes the stored data from UserDefaults.

Storage Implementation

The service uses a simple key-value approach:
All credentials are stored as JSON data under this single key in UserDefaults.

Data Format

Credentials are stored as JSON-encoded R2Credentials objects:
Stored JSON format:

Usage Examples

Migration Support

The loadAll() method includes backward compatibility:
Both formats are automatically handled, with single credentials wrapped in an array.

Security Considerations

Security Trade-offsThis implementation prioritizes simplicity for a personal tool:
  • Pro: Simple implementation, no Keychain complexity
  • Pro: Appropriate for single-user, local-only tools
  • Con: Credentials stored in plain text
  • Con: Accessible to other processes with UserDefaults access
  • Con: Included in Time Machine backups
For production multi-user apps, use the system Keychain:

When to Use UserDefaults vs Keychain

Use UserDefaults (current approach) when:
  • Building a personal single-user tool
  • Credentials are for the user’s own accounts
  • Simplicity is more important than maximum security
  • Running on the user’s own trusted machine
Use Keychain when:
  • Building a production app for distribution
  • Handling credentials for multiple users
  • Need encrypted storage
  • Require secure credential synchronization
  • Need to prevent unauthorized access

Persistence Guarantees

UserDefaults automatically persists changes to disk, but synchronization is asynchronous. For critical operations, you can force synchronization:
However, this is rarely necessary as the system handles synchronization reliably.

Testing

For testing, you can use a separate UserDefaults suite: