スポンサーリンク

【Swift】Keychainクラスで自由にトークンを保存・取得する(コピペ可)

SwiftUI
スポンサーリンク

Keychainとは

Keychainは前回の記事で取り扱ったUserDefaultsとは異なり、アクセストークンやパスワードなどのセキュアな情報をアプリの内部ストレージに保存するための機能です。

Keychainは暗号化されて保存され、Keychainを作成したアプリケーションでのみアクセス可能なので、外部にデータが漏れることなくデータを内部に保存できます。

KeychainManagerクラスの実装

import Security
import Foundation

// 保存する値の種類
enum TokenType: String {
    case accessToken
    case refreshToken
}

// Keychainを扱うクラス
class KeychainManager {
    // アプリ固有識別子
    private let service = "com.xxx.yyy"

    // トークンを保存する関数 成功すればtrueを返す
    func saveToken(token: String?, type: TokenType) -> Bool {
        var success = true
        
        if let token = token, let tokenData = token.data(using: .utf8) {
            success = success && save(data: tokenData, account: type.rawValue)
        }
        
        return success
    }

    // トークン保存の実行
    private func save(data: Data, account: String) -> Bool {
        let query: [String: Any] = [kSecClass as String: kSecClassGenericPassword,
                                    kSecAttrService as String: service,
                                    kSecAttrAccount as String: account,
                                    kSecValueData as String: data]
        
        SecItemDelete(query as CFDictionary) // 既存のアイテムを削除
        let status = SecItemAdd(query as CFDictionary, nil)
        return status == errSecSuccess
    }

    // トークンを取得する関数
    func retrieveToken(type: TokenType) -> String? {
        return retrieveToken(for: type.rawValue)
    }

    // トークン取得処理
    private func retrieveToken(for account: String) -> String? {
        let query: [String: Any] = [kSecClass as String: kSecClassGenericPassword,
                                    kSecAttrService as String: service,
                                    kSecAttrAccount as String: account,
                                    kSecMatchLimit as String: kSecMatchLimitOne,
                                    kSecReturnData as String: kCFBooleanTrue as Any]
        
        var dataTypeRef: AnyObject?
        let status = SecItemCopyMatching(query as CFDictionary, &dataTypeRef)
        
        if status == errSecSuccess {
            if let data = dataTypeRef as? Data {
                return String(data: data, encoding: .utf8)
            }
        }
        return nil
    }

    // トークンを削除する関数
    func deleteToken(type: TokenType) -> Bool {
        return delete(for: type.rawValue)
    }
    
    // トークン削除処理
    private func delete(for account: String) -> Bool {
        let query: [String: Any] = [kSecClass as String: kSecClassGenericPassword,
                                    kSecAttrService as String: service,
                                    kSecAttrAccount as String: account]
        
        let status = SecItemDelete(query as CFDictionary)
        return status == errSecSuccess
    }
}

各関数に必要な値を渡せばkeychainに値が保存されます。
今回はアクセストークンとリフレッシュトークンを保存する想定にしていますが、他にも保存する値を増やす場合はenumに設定してください。

kSecClass: 保存するアイテムの種類を指定します。
kSecAttrService: 保存するアイテムを識別するための文字列。一般的にアプリ固有の文字列を利用します。
kSecAttrAccount: 保存するアイテムをさらに細分化して識別するための文字列。
kSecValueData: 実際に保存したいデータ。Data型で保存しています。

利用方法

let accessToken = "APIなどで取得したtoken"
let success = KeychainManager().saveToken(token: accessToken, type: .accessToken)
if success { // 成功時の処理 } else { // 失敗時の処理 }

if let retrievedToken = KeychainManager().retrieveToken(type: .accessToken) 
{ // 成功した時の処理 token = retrievedToken } else { // 失敗時の処理 }

let deletionSuccess = KeychainManager.deleteToken(type: .accessToken)
if deletionSuccess { // 成功時の処理 } else { // 失敗時の処理 }

各処理は失敗する可能性を見据えて失敗処理も書いておきましょう。

コメント

タイトルとURLをコピーしました