Swift - Locksmith loadDataForUserAccount fails sometimes?

佐手、 提交于 2020-02-06 23:59:09

问题


I have a strange bug that is occurring only on few user iPhones, details below -

The app consumes a universal framework (developed by ourself) to save accessToken and refreshToken after successful login to the Keychain. We are using Locksmith to achieve the functionality - Save, load data and delete when the user is logged out.

Everytime when the app is killed and launched or applicationWillEnterForeground, the tokens are refreshed with the help of a service call and are saved to keychain again. When the refreshToken expires (this token is valid for one month), user is notified that the app has not been used for a long time and he is logged out.

The actual problem is here that for only few users, the refresh mechanism fails even when they are using the app daily (i.e. not before completion of one month of the refreshToken). After verification with backend team, the refresh service is always up so I suspect the Locksmith loadDataForUserAccount but unable to reproduce the issue. Also, may users do NOT face the problem. Everything works normally as expected.

Can someone help me move further how to identify the cause?

Below is the code to refresh the accessToken and refreshToken

** Refresh token call From the App when app enters foreground or killed and launched**

if let mySession = ServiceLayer.sharedInstance.session {

            mySession.refresh {  result in

                switch result {
                case .failure(.authenticationFailure):

                    if isBackgroundFetch {
                        print("👤⚠️ Session refresh failed, user is now logged out.")
                        self.myService.logoutCurrentUser()

                        // Logout Current user
                        mySession.invalidate()

                        self.showLoginUI()
                    }
                    else {
                        // user accessToken is invalid but provide access to QR
                        // on the home screen. disable all other actions except logout button

                        self.showHomeScreen()
                    }

                default:
                    mySession.getAccessToken { result in
                        switch result {

                        case let .success(value):
                            print("Access Token from App Delegate \(value)")
                            myAccessToken = value


                        case let .failure(error):
                            print("❌ Failed to fetch AccessToken: \(error)")                            

                        }
                    }
                }
            }
        }

From the framework where the refresh mechanism is implemented

public func refresh(_ completion: @escaping (MyResult<String, MyError>) -> (Void)) {

        guard isValid else {
            completion(.failure(.invalidSession))
            return
        }

        getRefreshToken { result in

            switch result {
            case let .success(refreshToken):

                // Get new tokens.
                ServiceClient.requestJSON(ServiceRequest.refreshToken(refreshToken: refreshToken)) { result in

                    switch result {
                    case let .success(dictionary):
                        var newAccessToken: String?
                        var newRefreshToken: String?

                        for (key, value) in dictionary {
                            if key as! String == "access_token" {
                                newAccessToken = value as? String
                            }
                            if key as! String == "refresh_token" {
                                newRefreshToken = value as? String
                            }
                        }

                        guard newAccessToken != nil && newRefreshToken != nil else {
                            completion(.failure(.general))
                            return
                        }

                        print("Renewed session tokens.")

                        do {
                            try Locksmith.updateData(data: [MySession.accessTokenKeychainKey: newAccessToken!, MySession.refreshTokenKeychainKey: newRefreshToken!],
                                                     forUserAccount: MySession.myKeychainAccount)
                        }
                        catch {
                            completion(.failure(.general))
                        }

                        completion(.success(newAccessToken!))

                    case let .failure(error):
                        if error == MyError.authenticationFailure {
                            print(“Session refresh failed due to authentication error; invalidating session.")
                            self.invalidate()
                        }

                        completion(.failure(error))
                    }

                }

            case let .failure(error):
                completion(.failure(error))
            }
        }
    }

回答1:


The app is likely being launched in the background while the device is locked (for app refresh or other background mode you've configured). Protected data (including Keychain) is not necessarily available at that time. You can check UIApplication.isProtectedDataAvailable to check if it's available, and you can reduce the protection of the item to kSecAttrAccessibleAfterFirstUnlock in order to have background access more reliably (though not 100% promised, even in that mode). Locksmith calls this AfterFirstUnlock.



来源:https://stackoverflow.com/questions/59532038/swift-locksmith-loaddataforuseraccount-fails-sometimes

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!