Skip to content

feat(ios): migrate auth storage to iOS Keychain - #244

Merged
2witstudios merged 2 commits into
masterfrom
feat/ios-keychain-migration
Jan 25, 2026
Merged

2witstudios merged 2 commits into
masterfrom
feat/ios-keychain-migration

Conversation

@2witstudios

@2witstudios 2witstudios commented Jan 25, 2026 •

Copy link
Copy Markdown
Owner

Summary

  • Migrates sensitive authentication data from NSUserDefaults (unencrypted) to iOS Keychain (encrypted)
  • Adds @aparajita/capacitor-secure-storage plugin for secure Keychain access
  • Implements automatic one-time migration for existing sessions with cleanup of legacy data

Security Improvements

  • Encrypted storage: Session tokens and CSRF tokens now stored in iOS Keychain
  • No iCloud sync: Tokens stay on-device only (disabled via setSynchronize(false))
  • Clean migration: Legacy NSUserDefaults data automatically migrated and removed

Changes

File Change
apps/ios/package.json Add @aparajita/capacitor-secure-storage dependency
apps/ios/src/auth-bridge.ts Replace Preferences with SecureStorage, add migration logic
apps/ios/src/index.ts Remove Preferences from public exports
Native files CocoaPods updated with KeychainSwift dependency

Test plan

  • Fresh install: Login flow works and session persists across app restarts
  • Migration: Install previous version → Login → Upgrade → Session persists
  • Logout: Clearing session removes data from Keychain
  • Verify tokens are NOT in NSUserDefaults after migration

Summary by CodeRabbit

Release Notes

  • New Features

    • Added authentication status checking to verify if a user has a valid active session.
  • Dependencies

    • Updated iOS platform dependencies with new browser and app integration libraries.
    • Integrated secure storage capability for enhanced data management on iOS.
  • Chores

    • Optimized iOS dependency management with fixed artifact resolution paths.

✏️ Tip: You can customize this high-level summary in your review settings.

Migrates sensitive authentication data from NSUserDefaults (unencrypted)
to iOS Keychain (encrypted) using @aparajita/capacitor-secure-storage.

Security improvements:
- Session tokens now stored in encrypted iOS Keychain
- iCloud sync disabled - tokens stay on-device only
- Automatic one-time migration preserves existing sessions
- Migration cleans up unencrypted legacy data from NSUserDefaults

Changes:
- Add @aparajita/capacitor-secure-storage dependency
- Update auth-bridge.ts with SecureStorage API and migration logic
- Remove Preferences from public exports (internal use only)
- Device ID remains in NSUserDefaults (non-sensitive)
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

@coderabbitai

coderabbitai Bot commented Jan 25, 2026 •

Copy link
Copy Markdown
Contributor

Warning

Rate limit exceeded

@2witstudios has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 1 minutes and 28 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📝 Walkthrough

Walkthrough

This pull request migrates iOS session and CSRF token storage from NSUserDefaults to iOS Keychain using a new SecureStorage integration, includes a one-time migration flow, adds related Capacitor dependencies, and removes the Preferences public API export.

Changes

Cohort / File(s) Summary
Dependency & Pod Configuration
apps/ios/package.json, apps/ios/ios/App/Podfile
Added new dependencies for @aparajita/capacitor-secure-storage, @capacitor/app, and @capacitor/browser; replaced dynamic pnpm-based pod resolutions with fixed paths; integrated AparajitaCapacitorSecureStorage pod.
Authentication Storage Migration
apps/ios/src/auth-bridge.ts
Introduced one-time migration from NSUserDefaults to iOS Keychain for session and CSRF tokens; added initSecureStorage and ensureMigrated helpers; replaced Preferences operations with SecureStorage; added new isAuthenticated() public API; migration runs before all read/write operations.
API Surface Update
apps/ios/src/index.ts
Removed export of Preferences from @capacitor/preferences.

Sequence Diagram(s)

sequenceDiagram
    participant App as App/Client
    participant AuthBridge as auth-bridge
    participant Flag as Migration Flag
    participant OldStorage as Preferences<br/>(NSUserDefaults)
    participant NewStorage as SecureStorage<br/>(Keychain)

    App->>AuthBridge: get/set/clear operation
    AuthBridge->>Flag: Check if already migrated?
    alt Migration not yet done
        Flag-->>AuthBridge: No migration
        AuthBridge->>OldStorage: Read session/CSRF data
        OldStorage-->>AuthBridge: Return legacy data
        AuthBridge->>NewStorage: Write data to Keychain
        NewStorage-->>AuthBridge: Success
        AuthBridge->>Flag: Mark as migrated
        Flag-->>AuthBridge: Flag updated
    end
    AuthBridge->>NewStorage: Perform requested operation
    NewStorage-->>AuthBridge: Return result
    AuthBridge-->>App: Return value
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Poem

🐰 From defaults to keychain, we hop with care,
One-time migration through iOS air,
Session tokens safe in vaults so deep,
The migration flag ensures no double sweep,
Secure storage blooms, NSUserDefaults rest! 🔐

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the main change: migrating authentication storage from NSUserDefaults to iOS Keychain using SecureStorage.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@2witstudios

Copy link
Copy Markdown
Owner Author

Code review

Found 2 issues:

  1. Missing error handling in migration function - SecureStorage operations can throw StorageError (e.g., keychain access denied) but ensureMigrated() has no try-catch. Since this function is called by all auth operations, an unhandled error would crash the app.

*/
async function ensureMigrated(): Promise<void> {
if (migrationComplete) return;
const { value: migrated } = await Preferences.get({ key: MIGRATED_KEY });
if (migrated === 'true') {
migrationComplete = true;
return;
}
await initSecureStorage();
// Migrate session from NSUserDefaults to Keychain
const { value: legacySession } = await Preferences.get({ key: AUTH_KEY });
if (legacySession) {
await SecureStorage.set(AUTH_KEY, legacySession, false, false);
await Preferences.remove({ key: AUTH_KEY });
console.log('[PageSpace iOS] Migrated session to Keychain');
}
// Migrate CSRF token from NSUserDefaults to Keychain
const { value: legacyCsrf } = await Preferences.get({ key: CSRF_KEY });
if (legacyCsrf) {
await SecureStorage.set(CSRF_KEY, legacyCsrf, false, false);
await Preferences.remove({ key: CSRF_KEY });
console.log('[PageSpace iOS] Migrated CSRF token to Keychain');
}
await Preferences.set({ key: MIGRATED_KEY, value: 'true' });
migrationComplete = true;
console.log('[PageSpace iOS] Keychain migration complete');
}

  1. Hardcoded version paths in Podfile - The resolve_pnpm_package helper function is defined but not used for pod paths. All pod definitions have hardcoded version strings (e.g., @capacitor+ios@7.4.5) that will break when dependencies update. The helper should be called instead: pod 'Capacitor', :path => resolve_pnpm_package('@capacitor/ios').

def capacitor_pods
pod 'Capacitor', :path => '../../../../node_modules/.pnpm/@capacitor+ios@7.4.5_@capacitor+core@7.4.5/node_modules/@capacitor/ios'
pod 'CapacitorCordova', :path => '../../../../node_modules/.pnpm/@capacitor+ios@7.4.5_@capacitor+core@7.4.5/node_modules/@capacitor/ios'
pod 'AparajitaCapacitorSecureStorage', :path => '../../../../node_modules/.pnpm/@aparajita+capacitor-secure-storage@7.1.6/node_modules/@aparajita/capacitor-secure-storage'
pod 'CapacitorApp', :path => '../../../../node_modules/.pnpm/@capacitor+app@7.1.1_@capacitor+core@7.4.5/node_modules/@capacitor/app'
pod 'CapacitorBrowser', :path => '../../../../node_modules/.pnpm/@capacitor+browser@7.0.3_@capacitor+core@7.4.5/node_modules/@capacitor/browser'
pod 'CapacitorKeyboard', :path => '../../../../node_modules/.pnpm/@capacitor+keyboard@7.0.4_@capacitor+core@7.4.5/node_modules/@capacitor/keyboard'
pod 'CapacitorPreferences', :path => '../../../../node_modules/.pnpm/@capacitor+preferences@7.0.3_@capacitor+core@7.4.5/node_modules/@capacitor/preferences'
pod 'CapacitorSplashScreen', :path => '../../../../node_modules/.pnpm/@capacitor+splash-screen@7.0.4_@capacitor+core@7.4.5/node_modules/@capacitor/splash-screen'
pod 'CapacitorStatusBar', :path => '../../../../node_modules/.pnpm/@capacitor+status-bar@7.0.4_@capacitor+core@7.4.5/node_modules/@capacitor/status-bar'
end


Generated with Claude Code

- If this code review was useful, please react with a thumbs up. Otherwise, react with a thumbs down.

Add try-catch error handling to all SecureStorage operations in
auth-bridge.ts to prevent crashes if Keychain access fails. Also
restore dynamic resolve_pnpm_package() calls in Podfile to handle
version changes without manual updates.
@2witstudios
2witstudios merged commit 3db91d1 into master Jan 25, 2026
3 checks passed
@2witstudios
2witstudios deleted the feat/ios-keychain-migration branch January 29, 2026 02:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant