{"id":1970,"url":"https://github.com/andriyslyusar/SwiftyKeychainKit","last_synced_at":"2025-08-02T05:33:23.056Z","repository":{"id":35141965,"uuid":"204202611","full_name":"andriyslyusar/SwiftyKeychainKit","owner":"andriyslyusar","description":"Modern Swift wrapper for Keychain Services API with the benefits of static typing","archived":false,"fork":false,"pushed_at":"2024-09-12T14:33:29.000Z","size":257,"stargazers_count":25,"open_issues_count":2,"forks_count":2,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-06-27T02:02:37.947Z","etag":null,"topics":["keychain","swift"],"latest_commit_sha":null,"homepage":"","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/andriyslyusar.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2019-08-24T19:24:23.000Z","updated_at":"2025-05-20T03:50:25.000Z","dependencies_parsed_at":"2024-07-27T21:31:43.209Z","dependency_job_id":"545a45be-e889-47f2-8dc3-6605d9b300ec","html_url":"https://github.com/andriyslyusar/SwiftyKeychainKit","commit_stats":null,"previous_names":[],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/andriyslyusar/SwiftyKeychainKit","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/andriyslyusar%2FSwiftyKeychainKit","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/andriyslyusar%2FSwiftyKeychainKit/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/andriyslyusar%2FSwiftyKeychainKit/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/andriyslyusar%2FSwiftyKeychainKit/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/andriyslyusar","download_url":"https://codeload.github.com/andriyslyusar/SwiftyKeychainKit/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/andriyslyusar%2FSwiftyKeychainKit/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":268339405,"owners_count":24234544,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-08-02T02:00:12.353Z","response_time":74,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["keychain","swift"],"created_at":"2024-01-05T20:16:00.191Z","updated_at":"2025-08-02T05:33:22.711Z","avatar_url":"https://github.com/andriyslyusar.png","language":"Swift","funding_links":[],"categories":["Security"],"sub_categories":["Keychain"],"readme":"# SwiftyKeychainKit\n\n![Platforms](https://img.shields.io/badge/platforms-ios%20-lightgrey.svg)\n[![Build Status](https://github.com/andriyslyusar/SwiftyKeychainKit/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/andriyslyusar/SwiftyKeychainKit/actions)\n![Swift version](https://img.shields.io/badge/swift-5.7%20%7C%205.6-orange.svg)\n[![SPM compatible](https://img.shields.io/badge/SPM-compatible-4BC51D.svg?style=flat)](#swift-package-manager)\n\nSwiftyKeychainKit is a simple Swift wrapper for Keychain Services API with the benefits of static typing. Define your keys in one place, use value types easily, and get extra safety and convenient compile-time checks for free.\n\n## Features\n* Static typing and compile-time checks\n* Support Gereric and Internet passwords\n* Throwing and Result type get methods\n* Easy way to implement support for custom types\n\n## Usage\n\n### Basic\n\n```swift\nlet keychain = Keychain(service: \"com.swifty.keychain\")\nlet accessTokenKey = Keychain.Key\u003cString\u003e(key: \"accessToken\")\n\n// Save or modify value\ntry? keychain.save(\"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9\", for : accessTokenKey)\n\n// Get value \nlet value = try keychain.get(accessTokenKey)\n\n// Remove value \ntry keychain.remove(accessTokenKey)\n\n// Remove all values \ntry keychain.removeAll()\n```\n\n### Instantiation\n\n```swift\n// Generic password\nlet keychain = Keychain(service: \"com.swifty.keychainkit\")\n\n// Internet password\nlet keychain = Keychain(server: URL(string: \"https://www.google.com\")!, protocolType: .https)\n```\n\n### Define keys \n\nFor extra convenience, define your keys by extending `KeychainKeys` class and adding static properties:\n\n```swift\nextension KeychainKeys {\n    static let username = Keychain.Key\u003cString\u003e(key: \"username\")\n    static let age = Keychain.Key\u003cInt\u003e(key: \"age\")\n}\n```\n\nand later in the code use shortcut dot syntax:\n\n```swift\n// save\ntry? keychain.save(\"John Snow\", for: .username)\n\n// get\nlet username = try keychain.get(.username)\n```\n\n### Geting values\n\nYou can use `subscripts` and `dynamicCallable` syntax sugar to get value as `Result\u003cValueType, KeychainError\u003e`\n\n```swift\nlet username = try keychain[.username].get()\n\n// or \n\nif case .success(let age) = keychain[.age] {\n    ...\n}\n```\n\n```swift\nlet username = try keychain(.username).get()\n\n// or \n\nif case .success(let age) = keychain(.age) {\n    ...\n}\n```\n\nBoth `subscripts` and `dynamicCallable` syntaxt available only for geting values. Currently Swift language limitation do not allow implement setter with error handling.\n\n### Default values\n\nYou can provide default value for get method and it will used than **keychain key is nil** and **no error throw**.\n\n```swift\ntry keychain.get(.username, default: \"Daenerys Targaryen\")\n\n// or\n\ntry keychain[.age, default: 18].get() \n```\n\n## SwiftUI\n\n### Basic\nUse @AppKeychainStorage property wrapper for storing and reading values from Keychain, which will automatically reinvoke your view’s body property when the value changes. \n* AppKeychainStorage property wrapper DOES NOT watches a key in KeyChain, and will not refresh your UI if that key changes. (Keychain on iOS lucking API to implement obsevation)\n* AppKeychainStorage property wrapper DOES NOT implement \"projected value\" Binding. The main reason is Swift lucking feature to implement throwable 'set', ...\n\n```swift\nstruct YourView: View {\n    @AppKeychainStorage(.secret) var secret\n\n    @State var secretInput: String = \"\"\n\n    var body: some View {\n        // Get value \n        switch secret {\n            case .success(let value):\n                if let value = value {\n                    Text(\"Keychain value: \\(value)\")\n                } else {\n                    Text(\"Keychain missing value\")\n                }\n\n            case .failure(let error):\n                Text(\"Keychain error: \\(error)\")\n        }\n\n        // Set value\n        TextField(\"Input secret\", text: $secretInput)\n        Button(\"Save\") {\n            do {\n                try self._secret.set(secretInput)\n            } catch {\n                // process error\n            }\n        }\n\n        // Remove value\n         Button(\"Remove\") {\n            do {\n                try self._secret.remove()\n            } catch {\n                // process error\n            }\n        }\n    }\n}\n```\n\n### Configure Keychain\nBesides the default keychain `@AppKeychainStorage` also supports any developer-defined Keychain.\n\n```swift\nstruct YourView: View {\n    @AppKeychainStorage(.secret, keychain: .accessGroupKeychain) var secret\n\n    var body: some View {\n        ...\n    }\n}\n\nextension Keychain {\n    // Keychain with custom access group to share Keychain item with extenstions\n    static let accessGroupKeychain = Keychain(accessGroup: \"NS8HLGG733.com.swifty.keychain.example\")\n}\n```\n\n ### Configure default Keychain\n Using `defaultAppKeychainStorage` modifier allows setting a default Keychain for the view or scene, avoiding the need to set it repeatedly in each `@AppKeychainStorage`\n\n ```swift\n @main\nstruct ExampleApp: App {\n    var body: some Scene {\n        WindowGroup {\n            YourView()\n                .defaultAppKeychainStorage(.accessGroupKeychain)\n        }\n        .defaultAppKeychainStorage(.accessGroupKeychain)\n    }\n}\n ```\n\n## Supported types\n- Int\n- String\n- Double\n- Float\n- Bool\n- Data\n\n### Codable\n```swift\nstruct MyStruct: Codable, KeychainSerializable { ... }\n```\n\n### NSCoding\n```swift\nclass MyClass: NSObject, NSCoding, KeychainSerializable { ... }\n```\n\n### Custom types\nIn order to save/get your own custom type that we don't support, you need to confirm it `KeychainSerializable` and implement `KeychainBridge` for this type.\n\nAs an example saving `Array\u003cString\u003e` using `JSONSerialization`:\n\n```swift \nextension Array: KeychainSerializable where Element == String  {\n    public static var bridge: KeychainBridge\u003c[String]\u003e { return KeychainBridgeStringArray() }\n}\n```\n\n```swift\nclass KeychainBridgeStringArray: KeychainBridge\u003c[String]\u003e {\n    public override func set(_ value: [String], forKey key: String, in keychain: Keychain) throws {\n        guard let data = try? JSONSerialization.data(withJSONObject: value, options: []) else {\n            fatalError()\n        }\n        try? persist(value: data, key: key, keychain: keychain)\n    }\n\n    public override func get(key: String, from keychain: Keychain) throws -\u003e [String]? {\n        guard let data = try? find(key: key, keychain: keychain) else {\n            return nil\n        }\n\n        return (try? JSONSerialization.jsonObject(with: data, options: [])) as? [String]\n    }\n}\n```\n\n## Supported attributes\n**Common**\n- [x] kSecAttrAccessGroup \n- [x] kSecAttrAccessible \n- [x] kSecAttrDescription\n- [x] kSecAttrComment\n- [x] kSecAttrCreator\n- [x] kSecAttrType\n- [x] kSecAttrLabel\n- [x] kSecAttrIsInvisible\n- [x] kSecAttrIsNegative\n- [x] kSecAttrAccount\n- [x] kSecAttrSynchronizable\n\n**Generic password**\n- [ ] kSecAttrAccessControl\n- [x] kSecAttrService \n- [x] kSecAttrGeneric\n\n**Internet password**\n- [x] kSecAttrSecurityDomain\n- [x] kSecAttrServer\n- [x] kSecAttrProtocol\n- [x] kSecAttrAuthenticationType\n- [x] kSecAttrPort\n- [x] kSecAttrPath\n\n\n## Requirement\n\n**Swift** version **5.0**\n\nPlatform     | Availability\n------------ | -------------\niOS          | \u003e= 12.0\nmacOS        | -\ntvOS         | - \nwatchOS      | -\n\n## Installation\n```swift\nlet package = Package(\n    dependencies: [\n        .Package(url: \"https://github.com/andriyslyusar/SwiftyKeychainKit.git\", .exact(\"1.0.0-beta.2\"))\n    ]\n)\n```\n\n## Acknowledgement \n* [SwiftyUserDefaults](https://github.com/sunshinejr/SwiftyUserDefaults) - API design inspiration\n* [KeychainAccess](https://github.com/kishikawakatsumi/KeychainAccess) - API wrapper knowledge source\n\n## Author\nAndriy Slyusar  \n* https://twitter.com/andriyslyusar\n* https://github.com/andriyslyusar \n\n## License\n\nSwiftyKeychainKit is available under the MIT license. See the [LICENSE file](https://github.com/andriyslyusar/SwiftyKeychainKit/blob/master/LICENSE) for more info.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fandriyslyusar%2FSwiftyKeychainKit","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fandriyslyusar%2FSwiftyKeychainKit","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fandriyslyusar%2FSwiftyKeychainKit/lists"}