not able to logout in pinterest in swift - ios

i am integrating Pinterest login in my swift app but the issue is that i am login success fully but when i click on button logout i am not able to logout here is my code
Code
#IBAction func btnLogin(_ sender: UIButton) {
PDKClient.sharedInstance().authenticate(withPermissions: [PDKClientReadPublicPermissions,PDKClientWritePublicPermissions,PDKClientReadRelationshipsPermissions,PDKClientWriteRelationshipsPermissions], withSuccess: { (PDKResponseObject) in
self.accessToken = PDKClient.sharedInstance().oauthToken
//MARK: - Getting User Profile, use "/v1/me" to get user data in the Response object -
let parameters : [String:String] =
[
"fields": "first_name,id,last_name,url,image,username,bio,counts,created_at,account_type" //these fields will be fetched for the loggd in user
]
PDKClient.sharedInstance().getPath("/v1/me/", parameters: parameters, withSuccess: {
(PDKResponseObject) in
self.view.isUserInteractionEnabled = true
self.user = (PDKResponseObject?.user())!
print(PDKResponseObject!)
self.lblUserName.text = PDKResponseObject?.user().username
if let url = JSON(PDKResponseObject?.user().images["60x60"] as Any)["url"].string
{
self.imgProPic.sd_setImage(with: URL(string: url))
}
}) {
(Error) in
if let error = Error
{
print("\(Error)")
}
self.view.isUserInteractionEnabled = true
PDKPinError.unknown
}
}) {
(Error) in
self.view.isUserInteractionEnabled = true
}
}
#IBAction func btnLogout(_ sender: UIButton) {
/*
let cookieJar : HTTPCookieStorage = HTTPCookieStorage.shared
for cookie in cookieJar.cookies! as [HTTPCookie]{
NSLog("cookie.domain = %#", cookie.domain)
if cookie.domain == "www.pinterest.com" ||
cookie.domain == "api.pinterest.com"{
cookieJar.deleteCookie(cookie)
}
}
*/
PDKClient.clearAuthorizedUser()
}
here i have done clearAuthorizedUser() but not logged out and also i have done cookie clear code but thats not working please help me to solve this

I had a very similar issue, and I thought I might be able to help someone by posting my solution here. The app that I'm maintaining authenticates Pinterest by loading "https://api.pinterest.com/oauth/" in a UIWebView with the appropriate parameters. This authentication approach does not require the Pinterest SDK and is explained in detail here: https://developers.pinterest.com/docs/api/overview/. I was able to add a button that the user can tap to log them out of their authenticated Pinterest account. To log the user out, I removed all cookies associated with Pinterest Authentication, and removed any stored authentication token. Below is the code I use to remove the cookies:
let cookieJar: HTTPCookieStorage = HTTPCookieStorage.shared
if let cookies: [HTTPCookie] = cookieJar.cookies {
for cookie in cookies {
let domain = cookie.domain
if domain == "accounts-oauth.pinterest.com"
|| domain == ".pinterest.com"
|| domain == ".facebook.com"
|| domain == "accounts.google.com" {
cookieJar.deleteCookie(cookie)
}
}
}
After that, you will most likely need to call PDKClient.clearAuthorizedUser() to remove any stored authentication token.

Related

ReCaptcha issue in Swift 5

i'm using this pod to use Google reCaptcha when users authenticate to an iOS app.
The captcha is shown, but not clickable.
in my loginViewController:
private var recaptcha: ReCaptcha?
private var locale: Locale = Locale(identifier: "fr-FR")
private var endpoint = ReCaptcha.Endpoint.default
override func viewDidLoad() {
super.viewDidLoad()
do {
recaptcha = try ReCaptcha(apiKey: Config.API_KEY, baseURL: URL(string: "http://localhost"), endpoint: .default, locale: locale)
}
catch {
print("error")
}
recaptcha?.configureWebView { [weak self] webview in
webview.frame = self?.view.bounds ?? CGRect.zero
}
recaptcha?.forceVisibleChallenge = true
}
func validate(){
recaptcha?.validate(on: view, resetOnError: true, completion: { (ReCaptchaResult) in
switch ReCaptchaResult {
case .error:
print("error")
case .token:
//WHAT TO DO WITH THE TOKEN HERE?
guard let userName = self.userNameTextField.text, let password = self.passwordTextField.text else {
return
}
self.authentificationPresenter.authenticate(userName: userName, password: password)
}
})
}
#IBAction func loginButtonTapped() {
validate()
}
The result is this. I don't understand how to go from here, and interpret the user's result.
Since the Captcha is loaded in a webview and to my knowledge would not be directly displayed on a ViewController is it possible that your Captcha is is somehow covered by an empty view? This may be why it is not "clickable". Either that or perhaps you need to enable interactions inside of your webview - you can try adding this when you configure the web view:
webview.isUserIntractionEnabled = true
As far as interpreting the result, if the user successfully completes the Captcha test you should receive a client token which you will use alongside your API secret key in a post request to the endpoint provided. Here is the documentation for verifying the user following the completion of a Captcha test: https://developers.google.com/recaptcha/docs/verify. Hope this helped.

RealmSwift: Implementing one time login with MongoDB Realm in swift

I want user to login once and not have to reenter their login info everytime they open app unless they logout in the last session.
Login screen is currently displayed everytime the app is open. This is my rootview
struct AppRootView: View {
var body: some View {
AnyView {
// check if user has already logged in here and then route them accordingly
if auth.token != nil {
homeMainView()
} else {
LoginController()
}
}
}
}
currently this is what I use to login users
#objc func signUp() {
setLoading(true);
app.usernamePasswordProviderClient().registerEmail(username!, password: password!, completion: {[weak self](error) in
// Completion handlers are not necessarily called on the UI thread.
// This call to DispatchQueue.main.sync ensures that any changes to the UI,
// namely disabling the loading indicator and navigating to the next page,
// are handled on the UI thread:
DispatchQueue.main.sync {
self!.setLoading(false);
guard error == nil else {
print("Signup failed: \(error!)")
self!.errorLabel.text = "Signup failed: \(error!.localizedDescription)"
return
}
print("Signup successful!")
// Registering just registers. Now we need to sign in, but we can reuse the existing username and password.
self!.errorLabel.text = "Signup successful! Signing in..."
self!.signIn()
}
})
}
#objc func signIn() {
print("Log in as user: \(username!)");
setLoading(true);
app.login(withCredential: AppCredentials(username: username!, password: password!)) { [weak self](maybeUser, error) in
DispatchQueue.main.sync {
self!.setLoading(false);
guard error == nil else {
// Auth error: user already exists? Try logging in as that user.
print("Login failed: \(error!)");
self!.errorLabel.text = "Login failed: \(error!.localizedDescription)"
return
}
guard let user = maybeUser else {
fatalError("Invalid user object?")
}
print("Login succeeded!");
//
let hostingController = UIHostingController(rootView: ContentView())
self?.navigationController?.pushViewController(hostingController, animated: true)
}
how could I implement one time login so that users do have to login each time they open the app?
A correctly configured and initialized RealmApp class will persist the session information for you between app restarts, you can check for an existing session using the .currentUser() method from this class. So in your case something like:
if app.currentUser() != nil {
homeMainView()
} else {
LoginController()
}
While using Realm to persist login is a good idea, but I would highly
advice against using it for managing user authentication credentials such
as passwords. A better approach if you want to save sensitive information is
using KeyChain just like what Apple and password manager apps do. With a light
weight keyChain wrapper library such as SwiftKeychainWrapper You can easily
save your login credentials in the most secure way.
Here is a sample using a keyChain wrapper linked above.
With simple modification you can use this helper class to manage your sign in credentials anywhere in your app.
import SwiftKeychainWrapper
class KeyChainService {
// Make a singleton
static let shared = KeyChainService()
// Strings which will be used to map data in keychain
private let passwordKey = "passwordKey"
private let emailKey = "emailKey"
private let signInTokenKey = "signInTokenKey"
// Saving sign in info to keyChain
func saveUserSignInInformation(
email: String,
password: String,
token: String
onError: #escaping() -> Void,
onSuccess: #escaping() -> Void
) {
DispatchQueue.global(qos: .default).async {
let passwordIsSaved: Bool = KeychainWrapper.standard.set(password, forKey: self.passwordKey)
let emailIsSaved: Bool = KeychainWrapper.standard.set(email, forKey: self.emailKey)
let tokenIsSaved: Bool = KeychainWrapper.standard.set(token, forKey: self.signInTokenKey)
DispatchQueue.main.async {
// Verify that everything is saved as expected.
if passwordIsSaved && emailIsSaved && tokenIsSaved {
onSuccess()
}else {
onError()
}
}
}
}
// Retrieve signIn information for auto login
func retrieveSignInInfo(onError: #escaping() -> Void, onSuccess: #escaping(UserModel) -> Void) {
DispatchQueue.main.async {
let retrievedPassword: String? = KeychainWrapper.standard.string(forKey: self.passwordKey)
let retrievedEmail: String? = KeychainWrapper.standard.string(forKey: self.emailKey)
let retrievedToken: String? = KeychainWrapper.standard.string(forKey: self.signInTokenKey)
if let password = retrievedPassword,
let email = retrievedEmail,
let token = retrievedToken {
// Assuming that you have a custom user model named "UserModel"
let user = UserModel(email: email, password: password,token: token)
// Here is your user info which you can use to verify with server if needed and auto login user.
onSuccess(user)
}else {
onError()
}
}
}
}

iOS: ADAL Auto sign-in after sign-out

I am facing an issue with auto sign-in with ADAL v2.5.4 in my iOS App.
When a user wants to login to MSA account, we call acquireTokenWithResource with the required params and promptBehavior as AD_PROMPT_AUTO.
In the first run of the app, the user is shown the webview from which login flow is working as expected as user is getting logged in successfully.
On clicking ‘Sign Out’ in my app, I am removing all tokens that have my app’s ClientID. At this point I see that there is still one token present in the cache with ClientID ‘foci-1’.
Additionally I’m clearing the cookie storage of my app so that the webview doesn’t reuse any the cookies.
The issue arises when the user wishes to login again. When the same flow is triggered again for login, now the user is automatically signed in. In the logs I see ‘1 token found for query’.
Ideally since the user signed out earlier, they should be prompted for their credentials again.
What is the right way to handle this scenario?
Should sign-out be handled differently? Should there be any additional checks before login is retriggered? What is the impact of promptBehavior in this scenario?
This is the code I use to perform a "logout" from an app that uses ADAL.
It calls the logout endpoint to invalidate the refresh token on the server side and deletes all of the relevant cookies and keychain entries.
fileprivate var safariModal = false
fileprivate var safariHostVC: UIViewController?
public func logout(presentOn viewController: UIViewController?, modal: Bool) {
let client = "xyzzy" // Your app client id here
let redirect = "youruri://somepath/" // Your redirect URI here
ADKeychainTokenCache.defaultKeychain().removeAll(forClientId: clientid, error: nil)
if let url = URL(string:"https://login.microsoftonline.com/common/oauth2/logout?post_logout_redirect_uri=\(redirect)") {
let safari = SFSafariViewController(url: url)
safari.toolbarItems = nil
safari.delegate = self
if #available(iOS 11.0, *) {
safari.dismissButtonStyle = .close
}
guard let vc = viewController else {
return
}
self.safariHostVC = vc
self.safariModal = modal
safari.modalPresentationStyle = .overFullScreen
safari.modalTransitionStyle = .coverVertical
if modal {
vc.present(safari, animated: true, completion: nil)
} else {
vc.navigationController?.pushViewController(safari, animated: true)
}
let cookieJar = HTTPCookieStorage.shared
guard let cookies = cookieJar.cookies else { return }
let cookiesArr = Array(cookies)
for cookie: HTTPCookie in cookiesArr {
if (cookie.name == "SignInStateCookie" || cookie.name == "ESTSAUTHPERSISTENT" || cookie.name == "ESTSAUTHLIGHT" || cookie.name == "ESTSAUTH" || cookie.name == "ESTSSC") {
cookieJar.deleteCookie(cookie)
}
}
}
}
You also need to implement an SFSafariViewControllerDelegate function
func safariViewControllerDidFinish(_ controller: SFSafariViewController) {
guard let vc = self.safariHostVC else {
return
}
if self.safariModal {
vc.dismiss(animated: true, completion: nil)
} else {
vc.navigationController?.popViewController(animated: true)
}
self.safariHostVC = nil
}

iOS Swift 4 OAuth

I writing an app in Swift 4 that uses the Discogs API. As such, I require a user to have access to personal data on their Discogs account, so I am authenticating against their API using OAuthSwift. Currently, I am able to kick off the auth flow, sign in and return the an oauthToken and the oauthTokenSecret
Making a subsequent request to their https://api.discogs.com/oauth/identity I am returned a user object, so I am happy at this point I can sign in and make authenticated requests.
However, I do not understand how I can check if a user is authenticated when the app first starts up. Currently, I am not storing the response, instead I am making a call to the identity endpoint in nested callback
import UIKit
import OAuthSwift
class ViewController: UIViewController {
let oauthSwift = OAuth1Swift(
consumerKey: "foo",
consumerSecret: "bar",
requestTokenUrl: "https://api.discogs.com/oauth/request_token",
authorizeUrl: "https://www.discogs.com/oauth/authorize",
accessTokenUrl: "https://api.discogs.com/oauth/access_token"
)
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
view.backgroundColor = .white
kickOffAuthFlow()
}
fileprivate func kickOffAuthFlow() {
oauthSwift.authorizeURLHandler = SafariURLHandler(viewController: self, oauthSwift: oauthSwift)
guard let callbackURL = URL(string: "foo.bar.boobaz:/oauth_callback") else { return }
oauthSwift.authorize(withCallbackURL: callbackURL, success: { (credential, response, parameters) in
_ = self.oauthSwift.client.get("https://api.discogs.com/oauth/identity", success: { (response) in
guard let dataString = response.string else { return }
print(dataString)
}, failure: { (error) in
print("error")
})
}) { (error) in
print(error.localizedDescription)
}
}
}
What is best practice in this case? How should I store these tokens and how should I ensure once the user is logged in, they aren't forced to log in the next time the app is opened (providing the token hasn't expired, however that is a separate issue I am prepared to handle at a later point)
Coming from a web development background, I was able to just store a token in session storage, on load I would then check the exp on the token and request a new one or take some other action.
I have not quite grasped how this works in iOS development yet.
You have two options to store access token in local.
UserDefault
Keychain
1. UserDefault
Use UserDefault to store token in memory. When the app gets launch, check if the token is stored in userdafault. UserDefault is used as short memory storage where you can store small data. It remains in memory if you kill the app.
let tokenIdentifier = "TokenIdentifier"
func storeAccessToken(token: String) {
UserDefaults.standard.set(token, forKey: tokenIdentifier)
}
func checkUserLogin() {
if UserDefaults.standard.value(forKey: tokenIdentifier) != nil {
print("User is Login")
}
else {
print("User need to login")
}
}
check this for learn more about userdefault
https://swift3tutorials.com/swift-3-user-defaults/
https://www.hackingwithswift.com/example-code/system/how-to-save-user-settings-using-userdefaults
2. Keychain
Userdefault is not secure. An access token is a sensitive information which should be stored in a secure place. So storing the access token in user default is not the correct choice. You must store access token in the keychain. Use SwiftKeychainWrapper pod to store token in Keychain.
let tokenIdentifier = "TokenIdentifier"
func storeAccessToken(token: String) {
KeychainWrapper.standard.set(token, forKey: tokenIdentifier)
}
func checkUserLogin() {
let token: String? = KeychainWrapper.standard.string(forKey: tokenIdentifier)
if token != nil {
print("User is Login")
}
else {
print("User need to login")
}
}

How to check if user has valid Auth Session Firebase iOS?

I wanna check if the user has still a valid session, before I present the Home View controller of my app. I use the latest Firebase API. I think if I use the legacy, I'll be able to know this.
Here's what I did so far:
I posted my question on Slack community of Firebase, no one is answering. I found this one, but this is for Android: https://groups.google.com/forum/?hl=el#!topic/firebase-talk/4HdhDvVRqHc
I tried reading the docs of Firebase for iOS, but I can't seem to comprehend it: https://firebase.google.com/docs/reference/ios/firebaseauth/interface_f_i_r_auth
I tried typing in Xcode like this:
FIRApp().currentUser()
FIRUser().getCurrentUser()
But I can't seem to find that getCurrentUser function.
if FIRAuth.auth().currentUser != nil {
presentHome()
} else {
//User Not logged in
}
For updated SDK
if Auth.auth().currentUser != nil {
}
Updated answer
Solution for latest Firebase SDK - DOCS
// save a ref to the handler
private var authListener: AuthStateDidChangeListenerHandle?
// Check for auth status some where
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
authListener = Auth.auth().addStateDidChangeListener { (auth, user) in
if let user = user {
// User is signed in
// let the user in?
if user.isEmailVerified {
// Optional - check if the user verified their email too
// let the user in?
}
} else {
// No user
}
}
}
// Remove the listener once it's no longer needed
deinit {
if let listener = authListener {
Auth.auth().removeStateDidChangeListener(authListener)
}
}
Original solution
Solution in Swift 3
override func viewDidLoad() {
super.viewDidLoad()
FIRAuth.auth()!.addStateDidChangeListener() { auth, user in
if user != nil {
self.switchStoryboard()
}
}
}
Where switchStoryboard() is
func switchStoryboard() {
let storyboard = UIStoryboard(name: "NameOfStoryboard", bundle: nil)
let controller = storyboard.instantiateViewController(withIdentifier: "ViewControllerName") as UIViewController
self.present(controller, animated: true, completion: nil)
}
Source
Solution in Swift 4
override func viewDidLoad() {
super.viewDidLoad()
setupLoadingControllerUI()
checkIfUserIsSignedIn()
}
private func checkIfUserIsSignedIn() {
Auth.auth().addStateDidChangeListener { (auth, user) in
if user != nil {
// user is signed in
// go to feature controller
} else {
// user is not signed in
// go to login controller
}
}
}
if Auth.auth().currentUser?.uid != nil {
//user is logged in
}else{
//user is not logged in
}
While you can see if there is such a user using Auth.auth().currentUser, this will only be telling you if there was a user authenticated, regardless of whether that users account still exists or is valid.
Complete Solution
The real solution to this should be using Firebase's re-authentication:
open func reauthenticate(with credential: AuthCredential, completion: UserProfileChangeCallback? = nil)
This assures (upon the launch of the application) that the previously signed in / authenticated user still in fact is and can be authenticated through Firebase.
let user = Auth.auth().currentUser // Get the previously stored current user
var credential: AuthCredential
user?.reauthenticate(with: credential) { error in
if let error = error {
// An error happened.
} else {
// User re-authenticated.
}
}
override func viewDidLoad() {
FIRAuth.auth()!.addStateDidChangeListener() { auth, user in
// 2
if user != nil {
let vc = self.storyboard?.instantiateViewController(withIdentifier: "Home")
self.present(vc!, animated: true, completion: nil)
}
}
}
Source: https://www.raywenderlich.com/139322/firebase-tutorial-getting-started-2
An objective-c solution would be (iOS 11.4):
[FIRAuth.auth addAuthStateDidChangeListener:^(FIRAuth * _Nonnull auth, FIRUser * _Nullable user) {
if (user != nil) {
// your logic
}
}];
All the provided answers only check on currentUser. But you could check the auth session by simple user reload like below:
// Run on the background thread since this is just a Firestore user reload, But you could also directly run on the main thread.
DispatchQueue.global(qos: .background).async {
Auth.auth().currentUser?.reload(completion: { error in
if error != nil {
DispatchQueue.main.async {
// Authentication Error
// Do the required work on the main thread if necessary
}
} else {
log.info("User authentication successfull!")
}
})
}

Resources