daneden / Twift

🐦 An async Swift library for the Twitter v2 API 🚧 WIP

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

How to go about saving user's credentials?

damartin1 opened this issue · comments

Hey everyone, curious how you are implementing the saveUserCredentials function for the onTokenRefresh. I am storing the token in keychain, but when I extract the token and place it in a OAuth2User accessToken, I get an "UnAuthorized" from TwitterAPIError. Below is a snippet of what I am doing, please ignore how this is setup as I am just trying to get it to work initially. I can save and read from keychain and it provides me with the token when I print it out, but can't seem to save and authorize a user once they allow the app to use Twitter. Thoughts?

func getFollowing(results: Int){
Task{
do{
let getStoredToken = read(service: "access-token", account: "twitter")!
let storedAccessToken = String(data: getStoredToken, encoding: .utf8)!
let oauthUser = OAuth2User(accessToken: storedAccessToken, clientId: TWITTER_CLIENT_ID, scope: Set(OAuth2Scope.allCases))

            print(storedAccessToken)
            
            if(storedAccessToken.isEmpty){
                let oauthUser = try await Twift.Authentication().authenticateUser(
                  clientId: TWITTER_CLIENT_ID,
                  redirectUri: URL(string: TWITTER_CALLBACK_URL)!,
                  scope: Set(OAuth2Scope.allCases)
                )
                print(oauthUser.accessToken)
            }
            
            var twitterClient = await Twift(oauth2User: oauthUser, onTokenRefresh: save)
          
            save(oauthUser: oauthUser)
            
            let result = try await twitterClient.getMe(fields: allUserFields)
            let following = try await twitterClient.getFollowing((result.data.id), maxResults: results)
            users = following.data
        }   catch{
            print(String(describing: error))
        }
    }
    
}

func save(oauthUser: OAuth2User) {
let data = Data(oauthUser.accessToken.utf8)
let service = "access-token"
let account = "twitter"

    // Create query
    let query = [
        kSecValueData: data,
        kSecClass: kSecClassGenericPassword,
        kSecAttrService: service,
        kSecAttrAccount: account,
    ] as CFDictionary
    
    // Add data in query to keychain
    let status = SecItemAdd(query, nil)
    
    if status == errSecDuplicateItem {
            // Item already exist, thus update it.
            let query = [
                kSecAttrService: service,
                kSecAttrAccount: account,
                kSecClass: kSecClassGenericPassword,
            ] as CFDictionary

            let attributesToUpdate = [kSecValueData: data] as CFDictionary

            // Update existing item
            SecItemUpdate(query, attributesToUpdate)
        }
}

func read(service: String, account: String) -> Data? {
    
    let query = [
        kSecAttrService: service,
        kSecAttrAccount: account,
        kSecClass: kSecClassGenericPassword,
        kSecReturnData: true
    ] as CFDictionary
    
    var result: AnyObject?
    SecItemCopyMatching(query, &result)
    
    return (result as? Data)
}
commented

This preview broke in few spots, so fairly hard to follow 🥲, but it kinda seems like it should work? 🤷‍♂️ You get the authUser and save it on refresh. Maybe store it before refresh as well?
It could happen that you get the user, and before refresh it is not saved?

I also write fairly linear when trying to make things work, but it is hard to know which part of the code here fell appart.

I can post what I have in my networking class, hopefully it helps? Saving in UserDefaults for now because its so convenient 😂

private let urlScheme = Constants.urlScheme
  private let clientId = Constants.clientID
  
  var client: Twift?
  
  func initiateLogin(forceReconnect: Bool = false, completion: @escaping (Bool)->Void) {
    Task {
        await loginUser(forceReconnect: forceReconnect, completion: { user in
            UserDefaults.encodeUser(user: user)
            self.initClient(withCredentials: user)
        completion(true)
      })
    }
  }
  
    func loginUser(forceReconnect: Bool = false, completion: @escaping (OAuth2User)->Void) async {
    do {
      // flow with existing user
      if let oauthUser = UserDefaults.decodeUser(), !forceReconnect {
        completion(oauthUser)
      } else {
        // flow with new user
        let oauthUser = try await Twift.Authentication().authenticateUser(
          clientId: Constants.clientID,
          redirectUri: URL(string: Constants.urlScheme)!,
          scope: Set(OAuth2Scope.allCases),
          presentationContextProvider: nil)
        completion(oauthUser)
      }
    } catch {
      print(error.localizedDescription)
    }
  }
  
  func initClient(withCredentials oauthUser: OAuth2User) {
      client = Twift(oauth2User: oauthUser, onTokenRefresh: { newUser in
          UserDefaults.encodeUser(user: newUser)
      })
  }

Thanks will give it a shot! Appreciate the feedback.