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)
}
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.