<NME> Google.cs
<BEF> ﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;

using Android.Runtime;
#elif __IOS__
using Foundation;
#elif WINDOWS_UWP
		public GoogleApi(string identifier, string clientId, string clientSecret, HttpMessageHandler handler = null) : base(identifier, clientId, clientSecret, handler)
		{
			this.TokenUrl = "https://accounts.google.com/o/oauth2/token";
		}

		public Uri RedirectUrl { get; set; } = new Uri("http://localhost");
		/// <value>The server client identifier.</value>
		public string ServerClientId { get; set; }
		public static bool ForceNativeLogin { get; set; } =
#if __IOS || __ANDROID__
			true;
#else
			false;
#endif
		public static string NativeClientSecret = "native";
		/// <summary>
		/// Only use this Constructor for platforms using NativeAuth
		/// </summary>
		/// <param name="identifier">Identifier.</param>
		/// <param name="clientId">Client identifier.</param>
		/// <param name="handler">Handler.</param>
		public GoogleApi (string identifier, string clientId, HttpMessageHandler handler = null) : this (identifier, clientId, NativeClientSecret, handler)
		{

		}

		public GoogleApi (string identifier, string clientId, string clientSecret, HttpMessageHandler handler = null) : base (identifier, CleanseClientId (clientId), clientSecret, handler)
		{
			this.TokenUrl = "https://accounts.google.com/o/oauth2/token";
#if __UNIFIED__
	}

	public class GoogleAuthenticator : OAuthAuthenticator
	{
		public override string BaseUrl
		{
			get;
#if WINDOWS_UWP
            GoogleShowAuthenticator = async (authenticator) =>
            {
                var url = await authenticator.GetInitialUrl();
                var endURI = WebAuthenticationBroker.GetCurrentApplicationCallbackUri();
                var result = await WebAuthenticationBroker.AuthenticateAsync(WebAuthenticationOptions.UseTitle, url, new Uri("https://accounts.google.com/o/oauth2/approval"));

                if (result.ResponseStatus == WebAuthenticationStatus.UserCancel)
                {
                    authenticator.OnCancelled();
		{
			var data = await base.GetTokenPostData(clientSecret);
			data["scope"] = string.Join(" ", Scope);
			data["redirect_uri"] = RedirectUrl.AbsoluteUri;
			return data;
		}

		public override async Task<Uri> GetInitialUrl()
		{
		public static Action<string, string> OnLogOut { get; set; }
		protected override Task<OAuthAccount> GetAccountFromAuthCode (WebAuthenticator authenticator, string identifier)
		{
			var auth = authenticator as GoogleAuthenticator;
			//Native lib returns the auth token already
			if (IsUsingNative && auth.ClientSecret == NativeClientSecret) {
				return Task.FromResult (new OAuthAccount () {
					ExpiresIn = 3600,
					Created = DateTime.UtcNow,
					Scope = authenticator.Scope?.ToArray (),
					TokenType = "Bearer",
					Token = auth.AuthCode,
					//Android wont send a refresh
					RefreshToken = auth.RefreshToken ?? auth.AuthCode,
					ClientId = ClientId,
					Identifier = identifier,
					IdToken = auth.IdToken,
					UserData = {
						{"ServerToken", auth.ServerToken},
					}
				});
			}

			return base.GetAccountFromAuthCode (authenticator, identifier);
		}

		public async Task<GoogleUserProfile> GetUserInfo (bool forceRefresh = false)
		{
			string userInfoJson;
			if (forceRefresh || CurrentAccount == null || !CurrentAccount.UserData.TryGetValue ("userInfo", out userInfoJson)) {
				userInfoJson = await Get ("https://www.googleapis.com/oauth2/v1/userinfo?alt=json");
				CurrentAccount.UserData ["userInfo"] = userInfoJson;
				SaveAccount (CurrentAccount);
			}

			return Deserialize<GoogleUserProfile> (userInfoJson);

		}
		public override void ResetData ()
		{
			OnLogOut?.Invoke (ClientId, ClientSecret);
			base.ResetData ();
		}
	}

	public class GoogleAuthenticator : OAuthAuthenticator {
		/// <summary>
		/// Optional, used to get the ServerTokens. The server token will be added to account.UserData["ServerToken"]
		/// </summary>
		/// <value>The server client identifier.</value>
		public string ServerClientId { get; set; }
		public override string BaseUrl {
			get;
			set;
		} = "https://accounts.google.com/o/oauth2/auth";


		public override async Task<Dictionary<string, string>> GetTokenPostData (string clientSecret)
		{
			var data = await base.GetTokenPostData (clientSecret);
			data ["scope"] = string.Join (" ", Scope);
			data ["client_id"] = GetGoogleClientId (ClientId);
			if (data ["client_secret"] == "native")
				data.Remove ("client_secret");
			data ["redirect_uri"] = GetRedirectUrl ();
			return data;
		}
		public override Dictionary<string, string> GetInitialUrlQueryParameters ()
		{
			var data = base.GetInitialUrlQueryParameters ();
			data ["redirect_uri"] = GetRedirectUrl ();
			data ["client_id"] = GetGoogleClientId (ClientId);
			return data;
		}

		public static string GetGoogleClientId (string clientId) => string.IsNullOrWhiteSpace (clientId) ? null : $"{GoogleApi.CleanseClientId (clientId)}.apps.googleusercontent.com";

		public bool IsUsingNative { get; set; }
		public virtual string GetRedirectUrl ()
		{
#if __UNIFIED__
			//for google, the redirect is a reverse of the client ID
			return $"com.googleusercontent.apps.{ClientId}:/oauthredirect";
#elif WINDOWS_UWP
            return "urn:ietf:wg:oauth:2.0:oob";
#else
			if (IsUsingNative)
				return "";
#endif
			return RedirectUrl.OriginalString;
		}
		public string IdToken { get; set; }
		public string ServerToken { get; set; }
		public string RefreshToken { get; set; }
		public override bool CheckUrl (Uri url, Cookie [] cookies)
		{
			try {
				if (url == null || string.IsNullOrWhiteSpace (url.Query))
					return false;
				var parts = HttpUtility.ParseQueryString (url.Query);
				var code = parts ["code"];
				if (!string.IsNullOrWhiteSpace (code)) {
					Cookies = cookies?.Select (x => new CookieHolder { Domain = x.Domain, Path = x.Path, Name = x.Name, Value = x.Value }).ToArray ();
					FoundAuthCode (code);
					return true;
				}

			} catch (Exception ex) {
				Console.WriteLine (ex);
			}
			return false;
		}


		public static string UrlFromClientId (string clientId)
		{
			var parts = clientId.Split ('.');
			return string.Join (".", parts.Reverse ());
		}


		public override async Task<Uri> GetInitialUrl ()
		{
			var uri = await base.GetInitialUrl ();
			return new Uri (uri.AbsoluteUri + "&access_type=offline");
		}

		public void OnRecievedAuthCode (string authCode)
		{
			FoundAuthCode (authCode);
		}
	}

#if __MOBILE__
	[Preserve (AllMembers = true)]
#endif
	public class GoogleUserProfile {
		[JsonProperty ("id")]
		public string Id {
			get;
			set;
		}

		[JsonProperty ("email")]
		public string Email {
			get;
			set;
		}

		[JsonProperty ("verified_email")]
		public bool VerifiedEmail {
			get;
			set;
		}

		[JsonProperty ("name")]
		public string Name {
			get;
			set;
		}

		[JsonProperty ("given_name")]
		public string GivenName {
			get;
			set;
		}

		[JsonProperty ("family_name")]
		public string FamilyName {
			get;
			set;
		}

		[JsonProperty ("link")]
		public string Link {
			get;
			set;
		}

		[JsonProperty ("picture")]
		public string Picture {
			get;
			set;
		}

		[JsonProperty ("gender")]
		public string Gender {
			get;
			set;
		}

		[JsonProperty ("locale")]
		public string Locale {
			get;
			set;
		}

		[JsonProperty ("hd")]
		public string Hd {
			get;
			set;
		}
	}

}

<MSG> Google on iOS now uses he native SFSafariViewController

<DFF> @@ -4,6 +4,7 @@ using System.Linq;
 using System.Net;
 using System.Net.Http;
 using System.Text;
+using System.Web;
 using System.Threading.Tasks;
 using Newtonsoft.Json;
 
@@ -14,6 +15,9 @@ namespace SimpleAuth.Providers
 		public GoogleApi(string identifier, string clientId, string clientSecret, HttpMessageHandler handler = null) : base(identifier, clientId, clientSecret, handler)
 		{
 			this.TokenUrl = "https://accounts.google.com/o/oauth2/token";
+			#if __IOS__
+			this.CurrentShowAuthenticator = NativeSafariAuthenticator.ShowAuthenticator; 
+			#endif
 		}
 
 		public Uri RedirectUrl { get; set; } = new Uri("http://localhost");
@@ -44,7 +48,7 @@ namespace SimpleAuth.Providers
 	}
 
 	public class GoogleAuthenticator : OAuthAuthenticator
-	{
+	{ 
 		public override string BaseUrl
 		{
 			get;
@@ -61,9 +65,56 @@ namespace SimpleAuth.Providers
 		{
 			var data = await base.GetTokenPostData(clientSecret);
 			data["scope"] = string.Join(" ", Scope);
-			data["redirect_uri"] = RedirectUrl.AbsoluteUri;
+			data ["client_id"] = GetGoogleClientId (ClientId);
+			data["redirect_uri"] = GetRedirectUrl();
 			return data;
 		}
+		public override Dictionary<string, string> GetInitialUrlQueryParameters ()
+		{
+			var data = base.GetInitialUrlQueryParameters ();
+			data ["redirect_uri"] = GetRedirectUrl ();
+			data ["client_id"] = GetGoogleClientId (ClientId);
+			return data;
+		}
+
+		public static string GetGoogleClientId (string clientId) => $"{clientId}.apps.googleusercontent.com";
+
+		public virtual string GetRedirectUrl ()
+		{
+			//Only implemented for iOS/mac right now
+#if __UNIFIED__
+			//for google, the redirect is a reverse of the client ID
+			return $"com.googleusercontent.apps.{ClientId}:/oauthredirect";
+#endif
+			return RedirectUrl.AbsoluteUri;
+		}
+
+		public override bool CheckUrl (Uri url, Cookie [] cookies)
+		{
+			try {
+				if (url == null || string.IsNullOrWhiteSpace (url.Query))
+					return false;
+				var parts = HttpUtility.ParseQueryString (url.Query);
+				var code = parts ["code"];
+				if (!string.IsNullOrWhiteSpace (code)) {
+					Cookies = cookies?.Select (x => new CookieHolder { Domain = x.Domain, Path = x.Path, Name = x.Name, Value = x.Value }).ToArray ();
+					FoundAuthCode (code);
+					return true;
+				}
+
+			} catch (Exception ex) {
+				Console.WriteLine (ex);
+			}
+			return false;
+		}
+
+
+		public static string UrlFromClientId (string clientId)
+		{
+			var parts = clientId.Split ('.');
+			return string.Join (".", parts.Reverse ());
+		}
+
 
 		public override async Task<Uri> GetInitialUrl()
 		{

{'addition_count': 53, 'commit_subject': 'Google on iOS now uses he native SFSafariViewController', 'deletion_count': 2, 'file_extension': '.cs', 'lang': 'cs', 'license': 'apache-2.0', 'repo_name': 'Clancey/SimpleAuth'}