50
votes

I am trying to authorise a user for Google calendar API inside an IOS app. I am using the OAuth2 feature of Google to authenticate users. Authorisation page opens with a 403 error with the description:

This user-agent is not permitted to make OAuth authorisation request to Google as it is classified as an embedded user-agent (also known as a web-view). Per our policy, only browsers are permitted to make authorisation requests to Google. We offer several libraries and samples for native apps to perform authorisation request in browser.

I followed the same procedure which is mentioned in this link: https://developers.google.com/google-apps/calendar/quickstart/ios

Rather than seeing my code, it's better to look at this link: https://developers.google.com/google-apps/calendar/quickstart/ios because I copy-pasted the same thing in my application.

below are my clientId and keyChainItemName:

static NSString *const kKeychainItemName = @"Google Calendar API";
static NSString *const kClientID = @"954370342601-sgl8k0jrbqdeagea9v6vfu3tspte96ci.apps.googleusercontent.com";
13
Show your source code - Alexander Farber
What approach did you go with? - Bhumit Mehta
@Subbhu Did you get any solution for this? - Alok
I faced this error in Android. I solved by setting "User Agent" for local WebView settings i.e. String ua = "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:46.0) Gecko/20100101 Firefox/46.0"; webview.getSettings().setUserAgentString(ua); You can find more in respect of IOS here: forums.xamarin.com/discussion/86415/… - Kalu Khan Luhar
this policy sucks, we have an established webview based OAuth approach (multi-platform with server side control) for various third party apps. And we're supposed to put in some special browser flow for Google apps? Using a webview allows us to do certificate pinning with our server communication + it gives us more control to strip sensitive info from headers + we need to facilitate login to multiple accounts for the same third party app (not all third party APIs allow you to force prompt to login and so sometimes it is essential to clear the cookies using webview approach). - hmac

13 Answers

36
votes

In my case I was using native web view to login with Google, I find out the way that you should provide user agent to webview it was worked for me. Try below code I am sure it will worked.

Add the code in application didFinishLaunchingWithOptions

Objective C

 NSDictionary *dictionary = [NSDictionary dictionaryWithObjectsAndKeys:@"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36", @"UserAgent", nil];
 [[NSUserDefaults standardUserDefaults] registerDefaults:dictionary];

Swift 3.0

let dictionary = NSDictionary(object: "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36", forKey: "UserAgent" as NSCopying)
UserDefaults.standard.register(defaults: dictionary)
17
votes
<preference name="OverrideUserAgent" value="Mozilla/5.0 Google" />

I also face this issue on my cordova project. You can try this: Just add this to your config.xml, worked for me.

15
votes

The short answer is that Google has updated its security restrictions for OAuth flow. They are not going to allow native web-views to initiate OAuth flows, but rather are encouraging people to use the OS browsers to do so. In your case, you'll probably have to wait for the Google calendar SDK to update their code to obey the newly recommended flow. More information is available in the Google blog

EDIT : I have tried to create a cross platform plugin which wraps around the native Google sign-in SDKs for using in a Xamarin forms app. More information can be found here

9
votes

Got same issue. Resolved by setting following property to the webview object:

webview.getSettings().setUserAgentString("Chrome/56.0.0.0 Mobile");

Hope this will help.

8
votes

As mentioned in previous answers, SFSafariViewController is a way to go, but for those who still uses WKWebView for OAuth authorization there is a simple workaround.

Just change customUserAgent to either one from list or set it to some arbitrary value. After that disallowed_useragent error will disappear:

WKWebViewConfiguration *configuration = [[WKWebViewConfiguration alloc] init];
WKWebView *webView = [[WKWebView alloc] initWithFrame:CGRectZero configuration:configuration];
// Check for selector availability, as it is available only on iOS 9+
if ([webView respondsToSelector:@selector(setCustomUserAgent:)]) {
    webView.customUserAgent = @"MyCustomUserAgent";
}

For changing User-Agent in UIWebView you can check this answer.

But be careful, as some backend code can depend on User-Agent header value.

3
votes

By default, if you don't have any google app, the google SDK opens the login inside a UIWebView when we initiate the login by using the following method.

        [[GIDSignIn sharedInstance] signIn];

I just added one line before this, which is as follows.

        [[GIDSignIn sharedInstance] setAllowsSignInWithWebView:NO];

Now the google doesn't authorise using the UIWebView popup. Instead, it opens in the Safari browser. And now everything just works as the way it was.

3
votes

Google decided to not longer allow embedded browsers to handle the oAuth authentication. The best way is to use SFSafariViewController on iOS. Here is how it can be solved using the CloudRail SDK:

In Objective-C:

@implementation AppDelegate

  // This method will receive the redirect URI after the authentication process was
  // successfull
- (BOOL)application:(UIApplication *)application handleOpenURL:(NSURL *)url {

  // Here we pass the response to the SDK which will automatically
  // complete the authentication process.
  [[NSNotificationCenter defaultCenter] postNotificationName:@"kCloseSafariViewControllerNotification" object:url];

  return YES;
}

@end

and Swift:

// This method will receive the redirect URI after the authentication process was
// successfull
func application(application: UIApplication, openURL url: NSURL, sourceApplication: String?, annotation: AnyObject) -> Bool {

    if (sourceApplication == "com.apple.SafariViewService") {
        // Here we pass the response to the SDK which will automatically
        // complete the authentication process.
        NSNotificationCenter.defaultCenter().postNotificationName("kCloseSafariViewControllerNotification", object: url)
        return true
    }
    return true
}

The complete blog post covering this issue can be found here: Solving ‘disallowed_useragent’ for Google services

2
votes

Take a look at this issue. Use GTMAppAuth instead.

2
votes

There is a workaround for this issue after the recent change in Google OAuth policies.

After integrating the Google Sign and enabling Google Calendar API, I was able to work with Google Calendar API to fetch and add Calendar Events. We just have to set the authorizer for GTLServiceCalendar which is obtained after Google sign-in.

service.authorizer = user.authentication.fetcherAuthorizer()

Here is the code snippets of Google GIDSignIn, followed by fetching calendar events.

import GoogleAPIClient
import GTMOAuth2
import UIKit
import GoogleSignIn

class ViewController: UIViewController, GIDSignInUIDelegate, GIDSignInDelegate {

  private let kApiKey = "AIzaXXXXXXXXXXXXXXXXXXXXXXX"

  // If modifying these scopes, delete your previously saved credentials by
  // resetting the iOS simulator or uninstall the app.
  private let scopes = [kGTLAuthScopeCalendar]
  private let service = GTLServiceCalendar()

  override func viewDidLoad() {
      super.viewDidLoad()

      service.apiKey = kApiKey

      GIDSignIn.sharedInstance().uiDelegate = self
      GIDSignIn.sharedInstance().scopes = scopes
      GIDSignIn.sharedInstance().signIn()
      GIDSignIn.sharedInstance().delegate = self
  }


  func sign(_ signIn: GIDSignIn!, didSignInFor user: GIDGoogleUser!, withError error: Error!) {

      if user != nil {
           print("\(user)")
           service.authorizer = user.authentication.fetcherAuthorizer()
           fetchEvents()
      }
  }

 // Construct a query and get a list of upcoming events from the user calendar
   func fetchEvents() {

        let query = GTLQueryCalendar.queryForEventsList(withCalendarId: "primary")
        query?.maxResults = 20
        query?.singleEvents = true
        query?.orderBy = kGTLCalendarOrderByStartTime

        service.executeQuery(query!, delegate: self, didFinish: #selector(ViewController.displayResultWithTicket(ticket:finishedWithObject:error:)))
   }

// Display the start dates and event summaries in the UITextView
    func displayResultWithTicket(
         ticket: GTLServiceTicket,
        finishedWithObject response : GTLCalendarEvents,
        error : NSError?) {

        if let error = error {
            showAlert(title: "Error", message: error.localizedDescription)
            return
        }

        var eventString = ""

        if let events = response.items(), !events.isEmpty {
            for event in events as! [GTLCalendarEvent] {
                print(event)
             }
        } else
                print("No upcoming events found.")
        }
    }

    }

This is how my credentials section appear in Google Dev Console.

enter image description here

1
votes

After the google sign-in finishes the login, use the currentUser to get the fetcherAuthorizer, this can be used like an authorizer for the Google Drive Service.

After that you can use the Google Drive Service normally:

GIDGoogleUser *googleUser = [GIDSignIn sharedInstance].currentUser;

if(googleUser != nil){
    self.service.authorizer = googleUser.authentication.fetcherAuthorizer;
    [self listFiles];

}
1
votes

This is working for me

mWebView.getSettings().setUserAgentString("Mozilla/5.0 (Linux; Android 4.1.1; Galaxy Nexus Build/JRO03C) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.166 Mobile Safari/535.19");
-1
votes

I solved the issue it took 1 year :-) just kidding. just add the below line in preference in config.xml

<preference name="OverrideUserAgent" value="Mozilla/5.0 Google" />

Please note i solved this google login issue with Inapp browser.

Thanks

-1
votes

I was getting error: "403 Error - Thats an error. Error: disallowed_useragent" on Google Signin InAppBrowser and for my situation the fix was adding userAgent property like this in Webview:

 <WebView
 // other props here
  userAgent={"Chrome/56.0.0.0 Mobile"}
 />