Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Login failure using webview #163

Closed
anonrig opened this issue Mar 31, 2020 · 28 comments
Closed

Login failure using webview #163

anonrig opened this issue Mar 31, 2020 · 28 comments
Labels
invalid This doesn't seem right

Comments

@anonrig
Copy link
Contributor

anonrig commented Mar 31, 2020

I'm receiving the following error message on try? result.get() using instagramcontroller, any idea why @TheM4hd1 ?

"https://i.instagram.com/api/v1/accounts/current_user/.\nInvalid response.\nProcessing handler returned `nil`.\n403"
@anonrig
Copy link
Contributor Author

anonrig commented Mar 31, 2020

This is related to using a useragent, but don't know what the expectation is.

"Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1"

@sbertix
Copy link
Collaborator

sbertix commented Apr 1, 2020

Endpoints can only be called through an Instagram app user agent.
403 means you're forbidden to access that resource, in this specific case due to this.
Nothing we can fix, not a problem with SwiftyInsta 💪
@anonrig

@sbertix sbertix added the invalid This doesn't seem right label Apr 1, 2020
@sbertix sbertix closed this as completed Apr 1, 2020
@MariaJsn
Copy link

MariaJsn commented Apr 4, 2020

Hi @anonrig @sbertix
Which agent should I use to login successfully? I tried many of them but I get login failed message :(
Please can you help me?

Thanks,
Maria

@sbertix
Copy link
Collaborator

sbertix commented Apr 4, 2020

I suggest sticking to the default one, leaving it nil.
Any other valid user agent should work for logging in as long as it's not used for calling the endpoints, like above, cause they require their own custom Instagram app one. @MariaJsn

@MariaJsn
Copy link

MariaJsn commented Apr 4, 2020

But when I use nil then I see android advertisement and apple will not approve my app like this. @sbertix

@sbertix
Copy link
Collaborator

sbertix commented Apr 4, 2020

Unfortunately that's the supported user agent because it works, if you manage to find one that works just as well without displaying the ad, let us know, considering the ad cannot be removed through JavaScript injection either. @MariaJsn

Beware though that apps built with SwiftyInsta are definitely not App Store safe: you gain control of an entire user's account, no tokens, no authorization, no nothing, which is a big no no for the Review guidelines, other than the Instagram terms and conditions, meaning your app is gonna be rejected at one point, if not at first, because of this. Ad or no ad.

@TheM4hd1
Copy link
Owner

TheM4hd1 commented Apr 4, 2020

use this user agent for webView:
Mozilla/5.0 (iPhone; CPU iPhone OS 13_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148
and it displays the App Store ads, then use the default user agent for Private API, it works well.

@MariaJsn
Copy link

MariaJsn commented Apr 4, 2020

@TheM4hd1 @sbertix I used this user agent but I can login failed again.

My code:

let login = LoginWebViewController(userAgent: "Mozilla/5.0 (iPhone; CPU iPhone OS 13_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148", completionHandler: { controller, result in

    controller.dismiss(animated: true, completion: nil)
   
    // deal with authentication response.
    guard let (response, _) = try? result.get() else { return print("Login failed.") }
    print("Login successful.")
    // persist cache safely in the keychain for logging in again in the future.
    guard let key = response.persist() else { return print("`Authentication.Response` could not be persisted.") }
    
})

override func viewDidLoad() {
    super.viewDidLoad()
    
        if #available(iOS 13, *) {
            present(login, animated: true, completion: nil) // just swipe down to dismiss.
        } else {
            present(UINavigationController(rootViewController: login),  // already adds a `Cancel` button to dismiss it.
                    animated: true,
                    completion: nil)
        }
   
}

@TheM4hd1
Copy link
Owner

TheM4hd1 commented Apr 4, 2020

I tried it on iOS 12.0, iPhone 7, Simulator and iOS 13.3, iPhone XS, Real Device
both worked well, I don't see a problem.
I just tried hard coded user agent in LoginWebView', and left everything as default`
make sure to clean build the project and build again if you used hard coded changes.

@MariaJsn
Copy link

MariaJsn commented Apr 4, 2020

@TheM4hd1 so my code is wrong? also I insert the code inside Loginwebwiew class init method and it shows Google play advertisement. Is it possible can you show me your code which has working one?

public init(frame: CGRect, userAgent: String? = nil, didReachEndOfLoginFlow: (() -> Void)? = nil) {
    // delete all cookies.
    HTTPCookieStorage.shared.removeCookies(since: Date.distantPast)
    // update the process pool.
    let configuration = WKWebViewConfiguration()
    configuration.processPool = WKProcessPool()
    // init login.
    self.userAgent = "Mozilla/5.0 (iPhone; CPU iPhone OS 13_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148"//userAgent
    self.didReachEndOfLoginFlow = didReachEndOfLoginFlow
    super.init(frame: frame, configuration: configuration)
    self.navigationDelegate = self
}

@TheM4hd1
Copy link
Owner

TheM4hd1 commented Apr 4, 2020

again, leave everything as default,

me.customUserAgent = self?.userAgent

you can hard code the user agent here, leave everything as default, Clean Build the project and build again, no changes applies if you don't clean build.
if this worked for you probably there's a bug in initializers and we need to double check the codes.
but I tried this and worked without any issue.

@sbertix
Copy link
Collaborator

sbertix commented Apr 4, 2020

Custom user agents in LoginWebView are fine.
The error above, the 403, is clearly referenced when trying to call the current user, meaning the custom user agent was applied to the APIHandler as well by mistake.
Do you mind printing out your error, @MariaJsn? Cause if it's the same that started this issue, the problem is not in LoginWebView.

@MariaJsn

This comment has been minimized.

@sbertix
Copy link
Collaborator

sbertix commented Apr 4, 2020

Do you mind printing the login error you get? @MariaJsn
Instead of doing

// deal with authentication response.
guard let (response, _) = try? result.get() else { return print("Login failed.") }
//print("Login successful.")
// persist cache safely in the keychain for logging in again in the future.
guard let key = response.persist() else { return print("`Authentication.Response` could not be persisted.") }

Write

switch result {
    case .failure(let error): print(error)
    case .success(let response):
             guard let key = response.0.persist() else { print("No key.") }
             print(key)
}

And paste in here the output of print, please.

@MariaJsn
Copy link

MariaJsn commented Apr 4, 2020

@sbertix the error is
custom("https://i.instagram.com/api/v1/accounts/current_user/.\nInvalid response.\nProcessing handler returned nil.\n403")

@sbertix
Copy link
Collaborator

sbertix commented Apr 4, 2020

Yep, so same as above, meaning the issue is not in LoginWebView.
userAgent however is never set to the APIHandler inside LoginWebViewController, and User-Agent is just a header field, not a cookie or something that can be persisted throughout requests, so I'm really struggling to understand what the problem is.

I'll test some more and update you if I find something.

@MariaJsn

This comment has been minimized.

@sbertix
Copy link
Collaborator

sbertix commented Apr 4, 2020

Ok… so it's not an issue of the user agent per se. It seems like any custom user configuration causes a checkpoint to the user.

{
    "errorBody" : "Please log back in.",
    "errorTitle" : "You've Been Logged Out",
    "logoutReason" : 3,
    "message" : "login_required",
    "status" : "fail"
}

The cookies are set and everything, but when they're used to fetch anything from a given endpoint the user is forced to log out.
Maybe because Instagram knows we're using iPhone cookies with Android endpoints? Idk.
Genuinely the only solution that comes to mind is reverting support for custom user agents.
@TheM4hd1 @MariaJsn

@MariaJsn
Copy link

MariaJsn commented Apr 4, 2020

@sbertix @TheM4hd1
if I not wrong understand we do not have any solution for it :(
so how @TheM4hd1 run it in his phone? Is it related with the phone performance?

@sbertix
Copy link
Collaborator

sbertix commented Apr 4, 2020

Cause I feel like he either has that device he was testing, so it's somehow linked to his profile, or he used that user agent before Instagram changed things.

Again though: this is not a problem with LoginWebView, seems more like a problem with how SwiftyInsta deals with cookies, storage and requests in general (I'm not trying to promote anything, especially cause it's in super early stage, but, for instance, it's not a problem with Swiftagram WebViewAuthenticator).
But, due to the way SwiftyInsta was originally ported, and the way I updated it to 2.* trying to keep to @TheM4hd1's amazing work, instead of rewriting it from the ground up, the code is extremely complicated to follow, and it's not an apparent one, unfortunately.

@MariaJsn
Copy link

MariaJsn commented Apr 4, 2020

@TheM4hd1
What do you think?
Do you have any idea?
And is there any solution in your mind?

@TheM4hd1
Copy link
Owner

TheM4hd1 commented Apr 4, 2020

actually I was using an older version of SwiftyInsta, I'm not sure what version it is, because I installed it from development branch long time ago.
I updated to latest version and same error happened.
I'm at the version that there is no LoginWebViewController and it's only LoginWebView

this is the file, I replaced it with current version ( a few changes requires for error handling ), and it worked well.
so @sbertix can check easier now what the issue is ?

//
//  LoginWebView.swift
//  SwiftyInsta
//
//  Created by Stefano Bertagno on 07/19/2019.
//  Copyright © 2019 Mahdi. All rights reserved.
//

#if os(iOS)
import UIKit
import WebKit

// MARK: Views
@available(iOS 11, *)
public class LoginWebView: WKWebView, WKNavigationDelegate {
    /// Called when reaching the end of the login flow.
    ///  You should probably hide the `InstagramLoginWebView` and notify the user with an activity indicator.
    public var didReachEndOfLoginFlow: (() -> Void)?
    /// Called once the flow is completed.
    var completionHandler: ((Result<[HTTPCookie], Error>) -> Void)!

    // MARK: Init
    public init(frame: CGRect,
                configuration: WKWebViewConfiguration = .init(),
                didReachEndOfLoginFlow: (() -> Void)? = nil) {
        // update the process pool.
        let copy = configuration.copy() as? WKWebViewConfiguration ?? WKWebViewConfiguration()
        copy.processPool = WKProcessPool()
        // init login.
        self.didReachEndOfLoginFlow = didReachEndOfLoginFlow
        super.init(frame: frame, configuration: copy)
        self.navigationDelegate = self
    }

    @available(*, unavailable, message: "use `init(frame:configuration:didReachEndOfLoginFlow:didSuccessfullyLogIn:completionHandler:)` instead.")
    private override init(frame: CGRect, configuration: WKWebViewConfiguration) {
        fatalError("init(frame:, configuration:) has been removed")
    }
    @available(*, unavailable)
    public required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    // MARK: Log in
    func authenticate(completionHandler: @escaping (Result<[HTTPCookie], Error>) -> Void) {
        // update completion handler.
        self.completionHandler = completionHandler
        // wipe all cookies and wait to load.
        deleteAllCookies { [weak self] in
            guard let me = self else { return }
            guard let url = URL(string: "https://www.instagram.com/accounts/login/") else {
                return
            }
            // in some iOS versions, use-agent needs to be different.
            // this use-agent works on iOS 11.4 and iOS 12.0+
            // but it won't work on lower versions.
//            me.customUserAgent = ["(Linux; Android 5.0; iPhone Build/LRX21T)",
//                                  "AppleWebKit/537.36 (KHTML, like Gecko)",
//                                  "Chrome/70.0.3538.102 Mobile Safari/537.36"].joined(separator: " ")
            me.customUserAgent = "Mozilla/5.0 (iPhone; CPU iPhone OS 13_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148"
            // load request.
            me.load(URLRequest(url: url))
        }
    }

    // MARK: Clean cookies
    private func fetchCookies() {
        configuration.websiteDataStore.httpCookieStore.getAllCookies { [weak self] in
            self?.completionHandler?(.success($0))
        }
    }

    private func deleteAllCookies(completionHandler: @escaping () -> Void) {
        HTTPCookieStorage.shared.removeCookies(since: Date.distantPast)
        WKWebsiteDataStore.default().removeData(ofTypes: WKWebsiteDataStore.allWebsiteDataTypes(),
                                                modifiedSince: .distantPast,
                                                completionHandler: completionHandler)
    }

    // MARK: Navigation delegate
    public func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
        guard webView.url?.absoluteString == "https://www.instagram.com/" else { return }
        // notify user.
        didReachEndOfLoginFlow?()
        // fetch cookies.
        fetchCookies()
    }
}
#endif

@sbertix
Copy link
Collaborator

sbertix commented Apr 4, 2020

Wasn't this the early 2.0 version we ended up changing because it caused problem when trying to logging in with more than one account? @TheM4hd1

Either way, either by starting with this snippet or with WebViewAuthenticator in Swiftagram I'll try to push an update solving these issues.
Due to the Error being called on endpoints, though, I'm not confident changing just LoginWebView will make it work consistently: it might help for some cases, but not fix it for good.

@TheM4hd1
Copy link
Owner

TheM4hd1 commented Apr 4, 2020

yes I guess that's the version.
error 403 happens because @MariaJsn can't login via webView, after entering user/pass Instagram just returns to home page and ask to login again. so the login process failed and 403 happens.
but when I tried with this old one, after entering user/pass, it redirects to my feed (so login succeed) and I can run requests from APIHandler.

@sbertix
Copy link
Collaborator

sbertix commented Apr 4, 2020

Except 403 does not happen on LoginWebView, but on AuthenticationHandler @TheM4hd1.

So, like I said, the issue is not related to LoginWebView functionality per se, but probably the changes I had to make related to cookies and persistency to make it work.
I thought it might have to do with LoginWebView finishing too early but that's not the case, even when removing the "fetch cookie attempts", their actual value does not change, eventually.
It looks like it might be related to AuthenticationHandler non receiving the updated cookies, which was not an issue back then cause they were never properly cleared.

tl;dr: again, the 403 does not happen on LoginWebView but in AuthenticationHandler when asking for the current user, suggesting the issue relates to cookies now being scraped too thoroughly, not giving it a chance to load the resource, whereas back then the cookies were never actually deleted properly.

@TheM4hd1
Copy link
Owner

TheM4hd1 commented Apr 4, 2020

that's what I meant, changing userAgent cause the login fail.
when a successful login happens on LoginWebView it redirects you to instagram feed page.
but here is the problem now when changing userAgent, it doesn't redirects to instagram feed page, it ask you to login again. forget the AuthenticationHandler for a few seconds, LoginWebView can't login if userAgent changes.
it's not related to AuthenticationHandler, when a login fails on webView it's obvious that AuthenticationHandler can't send requests (403)
I'm saying the issue is on LoginWebView or the way we handling cookies. because when I tried replacing the older version with current one, it worked well.

@sbertix
Copy link
Collaborator

sbertix commented Apr 4, 2020

Yep, but the problem of the feed not appearing in the new version has nothing to do with it: it's actually a feature, and a good one. If you used a custom user agent in the current version but replaced row 118 of LoginWebView.swift with break, you'd still see the feed, you'd see you actually managed to login correctly! We just don't need to show the feed anymore when the cookies are found, cause it was "ugly" to display the feed for a split second, so I removed it. Again, it's completely unrelated to the problem and again: you would see the feed (try it 😊).
The login actually works for the LoginWebView. The problem is only related to cookies as in "the code related to LoginWebView functionality is right", what doesn't work is the way AuthenticationHandler is no longer able to read the cookies it needs, most likely they're applied after the call is made, and because, unlike the older version of your snippet, they're actually cleared from storage, they can't be accessed without actually being passed to it.
Trust me: I wrote this 😊 @TheM4hd1
And if you don't trust me, just try it yourself: remove row 118 and you'll see the feed and that you log in correctly (cause of course you do, otherwise you wouldn't be able to login into Instagram from that device, which is ludicrous to assume that Instagram would just block people to login from an iPhone 6s, for instance) and with the same cookies.

@sbertix
Copy link
Collaborator

sbertix commented Apr 4, 2020

actually I was using an older version of SwiftyInsta, I'm not sure what version it is, because I installed it from development branch long time ago.
I updated to latest version and same error happened.
I'm at the version that there is no LoginWebViewController and it's only LoginWebView

this is the file, I replaced it with current version ( a few changes requires for error handling ), and it worked well.
so @sbertix can check easier now what the issue is ?
[…]

This does not work either. It returns the same exact error as the new version @TheM4hd1.
Apparently it's neither a LoginWebView bug, nor a cookie one. It's a security feature implemented by Instagram a year ago or so judging by what I cam up googling

{
"errorBody" : "Please log back in.",
"errorTitle" : "You've Been Logged Out",
"logoutReason" : 3,
"message" : "login_required",
"status" : "fail"
}

As of lately, accounts marked as spammer, I'm assuming all of the ones we're trying to log in with, since I'm assuming we're all relying on Instagram Private APIs for our side projects and we're not just writing this as proof of concept, are never prompted the Open the Instagram app on your phone checkpoint anymore, and it simply won't move on: and it's not only account based, but also IP based.
Somehow the default user agent for LoginWebView goes through, but using a different one messes up with SwiftyInsta default user agent and cookies (not for the LoginWebView, which again is fine and actually completes logging in, but the actual requests), while it still works in different implementations, probably because their pattern hasn't been recognised by Instagram yet (e.g. Swiftagram).
You can all test this by using the custom init by explicitly passing as argument the default user agent and you'll see it'll still work, so that it's clear that nothing's wrong with the implementation of that.

We would need to find a new way to channel requests, move forward from the user agent used for them below, and we'd still be very likely to fail.

Instagram 85.0.0.21.100 Android (21/5.0.2; 480dpi; 1080x1776; Sony; C6603; C6603; qcom; ru_RU; 146536611)

tl;dr: nothing we can do. It's Instagram's fault.


"Fixes"

  • reverting to using the default user agent alone.

Disclaimer

To anyone reading this:
I hate being a "mod", but I had to lock the conversation, cause enough has been said already.
Since the issue is clearly not related to the LoginWebView anymore, please open a new one if you feel the need to investigate this further but please keep in mind there's nothing me or @TheM4hd1 can do to fix this.

Repository owner locked as off-topic and limited conversation to collaborators Apr 4, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
invalid This doesn't seem right
Projects
None yet
Development

No branches or pull requests

4 participants