Using cached Cognito identity from Xamarin - android

When I first log into my app, I go through the following code:
auth = new Xamarin.Auth.OAuth2Authenticator(
"my-google-client-id.apps.googleusercontent.com",
string.Empty,
"openid",
new System.Uri("https://accounts.google.com/o/oauth2/v2/auth"),
new System.Uri("com.enigmadream.storyvoque:/oauth2redirect"),
new System.Uri("https://www.googleapis.com/oauth2/v4/token"),
isUsingNativeUI: true);
auth.Completed += Auth_Completed;
StartActivity(auth.GetUI(this));
Which triggers this activity:
[Activity(Label = "GoodleAuthInterceptor")]
[IntentFilter(actions: new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryDefault, Intent.CategoryBrowsable },
DataSchemes = new[] { "com.enigmadream.storyvoque" }, DataPaths = new[] { "/oauth2redirect" })]
public class GoodleAuthInterceptor : Activity
{
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
Android.Net.Uri uri_android = Intent.Data;
Uri uri_netfx = new Uri(uri_android.ToString());
MainActivity.auth?.OnPageLoading(uri_netfx);
Finish();
}
}
And finally this code to link the account to Cognito:
private void Auth_Completed(object sender, Xamarin.Auth.AuthenticatorCompletedEventArgs e)
{
if (e.IsAuthenticated)
{
var idToken = e.Account.Properties["id_token"];
credentials.AddLogin("accounts.google.com", idToken);
AmazonCognitoIdentityClient cli = new AmazonCognitoIdentityClient(credentials, RegionEndpoint.USEast2);
var req = new Amazon.CognitoIdentity.Model.GetIdRequest();
req.Logins.Add("accounts.google.com", idToken);
req.IdentityPoolId = "us-east-2:79ebf8e1-97de-4d1c-959a-xxxxxxxxxxxx";
cli.GetIdAsync(req).ContinueWith((task) =>
{
if ((task.Status == TaskStatus.RanToCompletion) && (task.Result != null))
{
ShowMessage(string.Format("Identity {0} retrieved", task.Result.IdentityId));
}
else
ShowMessage(task.Exception.InnerException != null ? task.Exception.InnerException.Message : task.Exception.Message);
});
}
else
ShowMessage("Login cancelled");
}
This all works great, and after the login, I am able to use my identity/credentials to retrieve data from DynamoDB. With this object:
Amazon.DynamoDBv2.AmazonDynamoDBClient ddbc = new Amazon.DynamoDBv2.AmazonDynamoDBClient(credentials, RegionEndpoint.USEast2);
The second time I run my app, this code runs:
if (!string.IsNullOrEmpty(credentials.GetCachedIdentityId()) || credentials.CurrentLoginProviders.Length > 0)
{
if (!bDidLogin)
{
var idToken = credentials.GetIdentityId();
ShowMessage(string.Format("I still remember you're {0} ", idToken));
And if I try to use the credentials with DynamoDB (or anything, I assume) at this point, I get errors that I don't have access to the identity. I have to logout (credentials.Clear()) and login again to obtain proper credentials.
I could require that a user go through the whole login process every time my app runs, but that's a real pain because the Google login process requires the user to know how to manually close the web browser to get back to the application after authenticating. Is there something I'm missing about the purpose and usage of cached credentials? When I use most apps, they aren't requiring me to log into my Google account every time and close a web browser just to access their server resources.

It looks like the refresh token needs to be submitted back to the OAuth2 provider to get an updated id token to add to the credentials object. First I added some code to save and load the refresh_token in a config.json file:
private Dictionary<string, string> config;
const string CONFIG_FILE = "config.json";
private void Auth_Completed(object sender, Xamarin.Auth.AuthenticatorCompletedEventArgs e)
{
if (e.IsAuthenticated)
{
var idToken = e.Account.Properties["id_token"];
if (e.Account.Properties.ContainsKey("refresh_token"))
{
if (config == null)
config = new Dictionary<string, string>();
config["refresh_token"] = e.Account.Properties["refresh_token"];
WriteConfig();
}
credentials.AddLogin("accounts.google.com", idToken);
CognitoLogin(idToken).ContinueWith((t) =>
{
try
{
t.Wait();
}
catch (Exception ex)
{
ShowMessage(ex.Message);
}
});
}
else
ShowMessage("Login cancelled");
}
void WriteConfig()
{
using (var configWriter = new System.IO.StreamWriter(
Application.OpenFileOutput(CONFIG_FILE, Android.Content.FileCreationMode.Private)))
{
configWriter.Write(ThirdParty.Json.LitJson.JsonMapper.ToJson(config));
configWriter.Close();
}
}
public void Login()
{
try
{
if (!string.IsNullOrEmpty(credentials.GetCachedIdentityId()) || credentials.CurrentLoginProviders.Length > 0)
{
if (!bDidLogin)
{
var idToken = credentials.GetIdentityId();
if (ReadConfig())
{
LoginRefreshAsync().ContinueWith((t) =>
{
try
{
t.Wait();
if (!t.Result)
FullLogin();
}
catch (Exception ex)
{
ShowMessage(ex.Message);
}
});
}
else
{
credentials.Clear();
FullLogin();
}
}
}
else
FullLogin();
bDidLogin = true;
}
catch(Exception ex)
{
ShowMessage(string.Format("Error logging in: {0}", ex.Message));
}
}
private bool ReadConfig()
{
bool bFound = false;
foreach (string filename in Application.FileList())
if (string.Compare(filename, CONFIG_FILE, true) == 0)
{
bFound = true;
break;
}
if (!bFound)
return false;
using (var configReader = new System.IO.StreamReader(Application.OpenFileInput(CONFIG_FILE)))
{
config = ThirdParty.Json.LitJson.JsonMapper.ToObject<Dictionary<string, string>>(configReader.ReadToEnd());
return true;
}
}
Then refactored the code that initiates the interactive login into a separate function:
public void FullLogin()
{
auth = new Xamarin.Auth.OAuth2Authenticator(CLIENTID_GOOGLE, string.Empty, "openid",
new Uri("https://accounts.google.com/o/oauth2/v2/auth"),
new Uri("com.enigmadream.storyvoque:/oauth2redirect"),
new Uri("https://accounts.google.com/o/oauth2/token"),
isUsingNativeUI: true);
auth.Completed += Auth_Completed;
StartActivity(auth.GetUI(this));
}
Refactored the code that retrieves a Cognito identity into its own function:
private async Task CognitoLogin(string idToken)
{
AmazonCognitoIdentityClient cli = new AmazonCognitoIdentityClient(credentials, RegionEndpoint.USEast2);
var req = new Amazon.CognitoIdentity.Model.GetIdRequest();
req.Logins.Add("accounts.google.com", idToken);
req.IdentityPoolId = ID_POOL;
try
{
var result = await cli.GetIdAsync(req);
ShowMessage(string.Format("Identity {0} retrieved", result.IdentityId));
}
catch (Exception ex)
{
ShowMessage(ex.Message);
}
}
And finally implemented a function that can retrieve a new token based on the refresh token, insert it into the current Cognito credentials, and get an updated Cognito identity.
private async Task<bool> LoginRefreshAsync()
{
string tokenUrl = "https://accounts.google.com/o/oauth2/token";
try
{
using (System.Net.Http.HttpClient client = new System.Net.Http.HttpClient())
{
string contentString = string.Format(
"client_id={0}&grant_type=refresh_token&refresh_token={1}&",
Uri.EscapeDataString(CLIENTID_GOOGLE),
Uri.EscapeDataString(config["refresh_token"]));
System.Net.Http.HttpContent content = new System.Net.Http.ByteArrayContent(
System.Text.Encoding.UTF8.GetBytes(contentString));
content.Headers.Add("content-type", "application/x-www-form-urlencoded");
System.Net.Http.HttpResponseMessage msg = await client.PostAsync(tokenUrl, content);
string result = await msg.Content.ReadAsStringAsync();
string idToken = System.Json.JsonValue.Parse(result)["id_token"];
credentials.AddLogin("accounts.google.com", idToken);
/* EDIT -- discovered this is not necessary! */
// await CognitoLogin(idToken);
return true;
}
}
catch (Exception ex)
{
ShowMessage(ex.Message);
return false;
}
}
I'm not sure if this is optimal or even correct, but it seems to work. I can use the resulting credentials to access DynamoDB without having to prompt the user for permission/credentials again.

There's a very different solution I'm trying to fit with the other answer. But it's so different, I'm adding it as a separate answer.
It appears the problem was not so much related to needing to explicitly use a refresh token to get an updated access token (I think this is done implicitly), but rather needing to remember the identity token. So rather than include all the complexity of manually applying a refresh token, all that's needed is to store the identity token (which can be done in a way similar to how the refresh token was being stored). Then we just need to add that same identity token back to the credentials object when it's missing.
if (!string.IsNullOrEmpty(credentials.GetCachedIdentityId()) || credentials.CurrentLoginProviders.Length > 0)
{
if (config.Read())
{
if (config["id_token"] != null)
credentials.AddLogin(currentProvider.Name, config["id_token"]);
Edit: The problem of needing to use a refresh token does still exist. This code works if the token hasn't expired, but attempting to use these credentials after the token has expired will fail, so there is still some need to use a refresh token somehow in some cases.

Related

Updating Xamarin.Forms breaks Prism navigation for Android

I have a Xamarin app made up of several pages, and I'm using Prism with AutoFac. I'm unable to update Xamarin.Forms without breaking navigation on the Android project only. It works fine on iOS.
I started with Xamarin.Form 3.1, and I cannot update to anything beyond that. My main page is a login page - when that is successful I navigate to the home page like so:
try
{
await _navigationService.NavigateAsync(new Uri($"/NavigationPage/{nameof(HomePage)}", UriKind.Absolute));
}
catch (Exception e)
{
Log.Error(e);
}
The navigation is not throwing any exceptions, and I'm not picking up any errors anywhere. Release notes for Xamarin 3.2 doesn't provide any clues either. I don't even know if this is a Xamarin or Prism issue. A few days of debugging and I feel no closer to figuring this out.
Has anyone else experienced this? or have any idea what could be going wrong?
Edit 1:
I finally isolated the issue - the fix was to call BeginInvokeOnMainThread when I navigate. But a few things still don't make sense to me:
This should raise an exception, so I must be hiding it somewhere. Is there anything obvious in the code below [This is the first time I've used Async, so seems likely I'm doing something wrong there]?
Why did this work with Xamarin 3.1 on not later versions
My logging confirms that the original navigation code was running on the main thread, but it still failed.
The code:
We are doing client-side google authentication with Azure, if that is successful we navigate to the home page.
First step, we connect to GooglePlay and authenticate the user
public void Login(MobileServiceClient client, Action<string, bool> onLoginComplete)
{
_client = client;
_onLoginComplete = onLoginComplete;
var signInIntent = Auth.GoogleSignInApi.GetSignInIntent(_googleApiClient);
((MainActivity)_context).StartActivityForResult(signInIntent, 1);
_googleApiClient.Connect();
}
The result comes to OnActivityResult in MainActivity.cs:
protected override void OnActivityResult(int requestCode, Result resultCode, Intent data)
{
base.OnActivityResult(requestCode, resultCode, data);
if (requestCode == SignInId)
{
Log.Info("Received result from Google sign in");
var result = Auth.GoogleSignInApi.GetSignInResultFromIntent(data);
DroidLoginProvider.Instance.OnAuthCompleted(result);
}
}
Which calls the OnAuthCompleted method. There are a few paths here. If the token is valid we don't re-authenticate with Azure, and just retrieve the saved user details:
public void OnAuthCompleted(GoogleSignInResult result)
{
if (result.IsSuccess)
{
Log.Trace("Native google log in successful");
var signInAccount = result.SignInAccount;
var accounts = _accountStore.FindAccountsForService("monkey_chat");
if (accounts != null)
{
foreach (var acct in accounts)
{
if (acct.Properties.TryGetValue("token", out var azureToken) && acct.Properties.TryGetValue("email", out var email))
{
if (!IsTokenExpired(azureToken))
{
Log.Trace("Auth token is still valid");
_client.CurrentUser = new MobileServiceUser(acct.Username)
{
MobileServiceAuthenticationToken = azureToken
};
_onLoginComplete?.Invoke(email, true);
return;
}
Log.Trace("Auth token no longer valid");
}
}
}
// Authenticate with Azure & get a new token
var token = new JObject
{
["authorization_code"] = signInAccount.ServerAuthCode,
["id_token"] = signInAccount.IdToken
};
try
{
var mobileUser = Task.Run(async () =>
{
try
{
Log.Trace("Authenticating with Azure");
return await client.LoginAsync(MobileServiceAuthenticationProvider.Google, token).ConfigureAwait(false);
}
catch (Exception e)
{
Log.Error(e);
throw;
}
}).GetAwaiter().GetResult();
var account = new Account(_client.CurrentUser.UserId);
account.Properties.Add("token", _client.CurrentUser.MobileServiceAuthenticationToken);
account.Properties.Add("email", signInAccount.Email);
_accountStore.Save(account, "monkey_chat");
_googleUser = new GoogleUser
{
Name = signInAccount.DisplayName,
Email = signInAccount.Email,
Picture = new Uri((signInAccount.PhotoUrl != null
? $"{signInAccount.PhotoUrl}"
: $"https://autisticdating.net/imgs/profile-placeholder.jpg")),
UserId = SidHelper.ExtractUserId(mobileUser?.UserId),
UserToken = mobileUser?.MobileServiceAuthenticationToken
};
_onLoginComplete?.Invoke(signInAccount.Email, true);
}
catch (Exception ex)
{
_onLoginComplete?.Invoke(string.Empty, false);
Log.Error(ex);
}
}
else
{
_onLoginComplete?.Invoke(string.Empty, false);
}
}
My original OnLoginComplete[Not working]:
private async void OnLoginComplete(bool successful, bool isNewUser)
{
if (successful)
{
try
{
Log.Info("Starting navigation to home page");
await _navigationService.NavigateAsync(new Uri($"/NavigationPage/{nameof(HomePage)}", UriKind.Absolute)).GetAwaiter().GetResult();
}
catch (Exception e)
{
Log.Error(e);
}
}
}
New OnLoginComplete[Working]
private void OnLoginComplete(bool successful, bool isNewUser)
{
Device.BeginInvokeOnMainThread(() =>
{
if (successful)
{
try
{
Log.Info("Starting navigation to home page");
_navigationService.NavigateAsync(new Uri($"/NavigationPage/{nameof(HomePage)}", UriKind.Absolute)).GetAwaiter().GetResult();
}
catch (Exception e)
{
Log.Error(e);
}
}
});
}

Auto send push notification from app server

Is there any way to send a push notification from server when a user complete a task? For example: A todo app will notify on that date with push notification. I want to use firebase and firestore for storing user tokens.
Alarm manager can be a solution that I have found but I don't wanna use it.
Yes, you can use scheduler to send notification from server to your app:
You may follow my working code:
Emplement IJob:
public class SendNotificationViaFcm: IJob
{
public void Execute(IJobExecutionContext context)
{
bool isNotificationSent=false;
try
{
var taskToSendNotification = FirebaseCloudMessaging.SendMessage();
Task.WaitAll(taskToSendNotification);
isNotificationSent = taskToSendNotification.Result;
}
catch (Exception exception)
when (
exception is ObjectDisposedException || exception is ArgumentNullException ||
exception is AggregateException)
{
}
catch (Exception exception) when (exception is InvalidOperationException)
{
}
catch (Exception exception)
{
// ignored
}
}
}
Call FCM Api from your server:
public class FirebaseCloudMessaging
{
private static readonly Uri FcmUri = new Uri(
uriString: #"https://fcm.googleapis.com",
uriKind: UriKind.Absolute);
private const string FcmApiKey = "Your Legacy Server Key";
public static async Task<bool> SendMessage()
{
using (var httpClient = new HttpClient())
{
httpClient.BaseAddress = FcmUri;
httpClient.DefaultRequestHeaders.Clear();
httpClient.DefaultRequestHeaders.TryAddWithoutValidation("Authorization",
"key=" + FcmApiKey);
var response = await httpClient.PostAsJsonAsync(#"/fcm/send", new
{
to = "/topics/global",
priority = "high",
data = new
{
title = "Warning",
message = "Please start app to track movemoent!"
}
//to = "/topics/global",
//priority = "high",
//notification = new
//{
// title = "Warning!",
// body = "Please start app to track movemoent!"
//}
});
Debug.Write(response.Content.ReadAsStringAsync());
var ck = response.IsSuccessStatusCode;
return response.IsSuccessStatusCode;
}
}
}
Implement schedular for your time interval:
public class Scheduler
{
public static void Start()
{
try
{
IScheduler scheduler = StdSchedulerFactory.GetDefaultScheduler();
scheduler.Start();
// scheduler.Shutdown();
var sentCloudNotification = JobBuilder.Create<SendNotificationViaFcm>().Build();
var cloudNotificationTrigger = TriggerBuilder.Create().WithSimpleSchedule(x => x.WithIntervalInMinutes(1).RepeatForever()).Build();
scheduler.ScheduleJob(sentCloudNotification, cloudNotificationTrigger);
}
catch (SchedulerException exception)
{
Debug.Write(exception.Message);
}
catch (Exception exception)
{
Debug.Write(exception.Message);
}
}
}
Finally Run in from your Global.asax.cs
protected void Application_Start()
{
Scheduler.Start();
}
It sounds like you're looking for a tool that allows you to schedule transactional notifications. What sort of server technology are you using?
From a high level you could do something like this:
1) user adds a task in the Android application
2) android application sends request to server to save the task
3) you have some code that runs in some sort of on task save callback that schedules a block of code to run in the future using crontab, celery or something similar.
4) the block of code that runs in the future is an api call to twilio to send a push notification
relevant links: https://www.twilio.com, https://firebase.google.com/docs/cloud-messaging/, http://www.celeryproject.org/, https://en.wikipedia.org/wiki/Cron

OAuth1Authenticator of Xamarin.Auth not terminating not completing

I am currently trying to use a REST service inside a xamarin.forms app.
To perform the authentication I use this code:
string consumerKey = "consumer_key";
string consumerSecret = "consumer_secret";
var requestTokenUrl = new Uri("https://service/oauth/request_token");
var authorizeUrl = new Uri("https://dservice/oauth/authorize");
var accessTokenUrl = new Uri("https://service/oauth/access_token");
var callbackUrl = new Uri("customprot://oauth1redirect");
authenticator = new Xamarin.Auth.OAuth1Authenticator(consumerKey, consumerSecret, requestTokenUrl, authorizeUrl, accessTokenUrl, callbackUrl, null, true);
authenticator.ShowErrors = true;
authenticator.Completed += Aut_Completed;
var presenter = new Xamarin.Auth.Presenters.OAuthLoginPresenter();
presenter.Completed += Presenter_Completed;
authenticator.Error += Authenticator_Error;
presenter.Login(authenticator);
Now, after authenticating the user will be redirected to customprot://oauth1redirect. To catch this redirection I added a new IntentFilter (for Android) like this:
[Activity(Label = "OAuthLoginUrlSchemeInterceptorActivity", NoHistory = true, LaunchMode = LaunchMode.SingleTop)]
[IntentFilter(
new[] { Intent.ActionView },
Categories = new[] { Intent.CategoryDefault, Intent.CategoryBrowsable },
DataSchemes = new[] { "customprot"},
DataPathPrefix = "/oauth1redirect")]
public class OAuthLoginUrlSchemeInterceptorActivity : Activity
{
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
// Convert Android.Net.Url to Uri
var uri = new Uri(Intent.Data.ToString());
// Load redirectUrl page
Core.Controller.authenticator.OnPageLoading(uri);
Core.Controller.authenticator.OnPageLoaded(uri);
Finish();
}
}
As far as I understood the documentation of xamarin.auth this will trigger the OAuth1Authenticator to parse the resulting url to get the authenticated user's credentials, and ultimatley triggering the Completed or Error event. But suprisingly nothing happens: no event is called or error raised. As this makes debugging harder, I do not really know how to solve this issue. Therefore, I am looking for suggestings about the cause of the issue and possible solutions, too.
Edit: Just to make this clearer: The OnCreate method of the intent is called, but executing the OnPageLoading method does not raise the Completed nor the Error event of the authenticator.
Edit2: here is the code of my callbacks (I created a breakpoint inside each of them, and the debugger does not break at them or raise an exception, so I am quite sure, that the callbacks are not called at all).
private static void Presenter_Completed(object sender, Xamarin.Auth.AuthenticatorCompletedEventArgs e)
{
throw new NotImplementedException();
}
private static void Aut_Completed(object sender, Xamarin.Auth.AuthenticatorCompletedEventArgs e)
{
throw new NotImplementedException();
}
This may only help future people (like me) that stumble on this question but perhaps not answer your particular issue. I was experiencing the same symptoms using the OAuth2Authenticator. I was capturing the redirect, calling OnPageLoading(), but then neither my completed or error events were firing.
The key for me was that it was only happening the 2nd time I called the Authenticator.
After digging through the Xamarin.Auth source, I realized that if HasCompleted is true when the authenticator calls OnSucceeded(), it simply returns without raising any events:
From Authenticator.cs
public void OnSucceeded(Account account)
{
string msg = null;
#if DEBUG
string d = string.Join(" ; ", account.Properties.Select(x => x.Key + "=" + x.Value));
msg = String.Format("Authenticator.OnSucceded {0}", d);
System.Diagnostics.Debug.WriteLine(msg);
#endif
if (HasCompleted)
{
return;
}
HasCompleted = true;
etc...
So, my issue was that I was keeping my authenticator instance around. Since HasCompleted is a private set property, I had to create a new authenticator instance and now it all works as expected.
Maybe I should have posted a new question and answered it. I'm sure the community will let me know.
I have also run into this issue but after managed to get this part working
I create my OAuth2Authenticatoras follows:
App.OAuth2Authenticator = new OAuth2Authenticator(
clientId: OAuthConstants.CLIENT_ID,
clientSecret: null,
scope: OAuthConstants.SCOPE,
authorizeUrl: new Uri(OAuthConstants.AUTHORIZE_URL),
accessTokenUrl: new Uri(OAuthConstants.ACCESS_TOKEN_URL),
redirectUrl: new Uri(OAuthConstants.REDIRECT_URL), //"com.something.myapp:/oauth2redirect" -- note I only have one /
getUsernameAsync: null,
isUsingNativeUI: true);
then in my Interceptor activity:
[Activity(Label = "GoogleAuthInterceptor")]
[IntentFilter
(
actions: new[] { Intent.ActionView },
Categories = new[]
{
Intent.CategoryDefault,
Intent.CategoryBrowsable
},
DataSchemes = new[]
{
// First part of the redirect url (Package name)
"com.something.myapp"
},
DataPaths = new[]
{
// Second part of the redirect url (Path)
"/oauth2redirect"
}
)]
public class GoogleAuthInterceptor: Activity
{
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
// Create your application here
Android.Net.Uri uri_android = Intent.Data;
// Convert Android Url to C#/netxf/BCL System.Uri
Uri uri_netfx = new Uri(uri_android.ToString());
// Send the URI to the Authenticator for continuation
App.OAuth2Authenticator?.OnPageLoading(uri_netfx);
// remove your OnPageLoaded it results in an invalid_grant exception for me
Finish();
}
}
You can try changing your DataPathPrefix = "/oauth1redirect")] to
DataPaths = new[]
{
// Second part of the redirect url (Path)
"/oauth1redirect"
}
This successfully trigger the Completed event on the OAuth2Authenticator and then after that the one on the presenter
private async void OAuth2Authenticator_Completed(object sender, AuthenticatorCompletedEventArgs e)
{
try
{
// UI presented, so it's up to us to dimiss it on Android
// dismiss Activity with WebView or CustomTabs
if(e.IsAuthenticated)
{
App.Account = e.Account;
var oAuthUser = await GetUserDetails();
// Add account to store
AccountStore.Create().Save(App.Account, App.APP_NAME_KEY);
}
else
{
// The user is not authenticated
// Show Alert user not found... or do new signup?
await App.Notify("Invalid user. Please try again");
}
}
catch(Exception ex)
{
throw;
}
}
At this stage I am redirected to the App.
I am currently trying to solve an issue where the presenter is not closed. It runs in the background even though the app is in the foreground and the user already authenticated. But this should hopefully help you solve your issue.

Xamarin.Auth : Authenticating Users with an Identity Provider [xamarin forms - Android]

I'm following this sample tutorial for https://developer.xamarin.com/guides/xamarin-forms/cloud-services/authentication/oauth/
After download the sample, I follow the instructions and change all my clientID endpoint.I got into the Google Sign In page and browser manage to close. After browser close, it always got into OnAuthError and error message is "Error authenticating : invalid_request"
I couldn't get the OnAuthCompleted fired. Its always got into OnAuthError.
void OnLoginClicked(object sender, EventArgs e)
{
string clientId = null;
string redirectUri = null;
switch (Device.RuntimePlatform)
{
case Device.iOS:
clientId = Constants.iOSClientId;
redirectUri = Constants.iOSRedirectUrl;
break;
case Device.Android:
clientId = Constants.AndroidClientId;
redirectUri = Constants.AndroidRedirectUrl;
break;
}
var authenticator = new OAuth2Authenticator(
clientId,
null,
Constants.Scope,
new Uri(Constants.AuthorizeUrl),
new Uri(redirectUri),
new Uri(Constants.AccessTokenUrl),
null,
true);
authenticator.Completed += OnAuthCompleted;
authenticator.Error += OnAuthError;
AuthenticationState.Authenticator = authenticator;
var presenter = new Xamarin.Auth.Presenters.OAuthLoginPresenter();
presenter.Login(authenticator);
}
async void OnAuthCompleted(object sender, AuthenticatorCompletedEventArgs e)
{
var authenticator = sender as OAuth2Authenticator;
if (authenticator != null)
{
authenticator.Completed -= OnAuthCompleted;
authenticator.Error -= OnAuthError;
}
User user = null;
if (e.IsAuthenticated)
{
// If the user is authenticated, request their basic user data from Google
// UserInfoUrl = https://www.googleapis.com/oauth2/v2/userinfo
var request = new OAuth2Request("GET", new Uri(Constants.UserInfoUrl), null, e.Account);
var response = await request.GetResponseAsync();
if (response != null)
{
// Deserialize the data and store it in the account store
// The users email address will be used to identify data in SimpleDB
string userJson = await response.GetResponseTextAsync();
user = JsonConvert.DeserializeObject<User>(userJson);
}
if (account != null)
{
store.Delete(account, Constants.AppName);
}
await store.SaveAsync(account = e.Account, Constants.AppName);
await DisplayAlert("Email address", user.Email, "OK");
}
}
void OnAuthError(object sender, AuthenticatorErrorEventArgs e)
{
var authenticator = sender as OAuth2Authenticator;
if (authenticator != null)
{
authenticator.Completed -= OnAuthCompleted;
authenticator.Error -= OnAuthError;
}
Debug.WriteLine("Authentication error: " + e.Message);
}
I have read through all the related links. The closest questions I got from SO is this : Xamarin.Forms Google API Authenticating Users with an Identity Provider
but it still failed after configure the setting and update my packages. I'm using Xamarin.Auth 1.5.0.3 (latest stable version)
Any real hero outside can run this tutorial and make it work?
The problem is that I am using Type : "Others" at the Google Console Developer website. When I switch back the Type: "Android". It's working.

Couchbase facebook pull authenticator

I am using couchbase mobile for an application and I want to use facebook for authentication. As per documentation, couchbase offers it's own implementation for authentication, the only required thing would be the token which I retrieve from the android facebook login flow.
The code for Synchronize class looks something like this:
public class Synchronize {
public Replication pullReplication;
public Replication pushReplication;
public static class Builder {
public Replication pullReplication;
public Replication pushReplication;
public Builder(Database database, String url, Boolean continuousPull) {
if (pullReplication == null && pushReplication == null) {
URL syncUrl;
try {
syncUrl = new URL(url);
} catch (MalformedURLException e) {
throw new RuntimeException(e);
}
pullReplication = database.createPullReplication(syncUrl);
pullReplication.setContinuous(true);
pushReplication = database.createPushReplication(syncUrl);
pushReplication.setContinuous(true);
}
}
public Builder facebookAuth(String token) {
if (!TextUtils.isEmpty(token)) {
Authenticator facebookAuthenticator = AuthenticatorFactory.createFacebookAuthenticator(token);
pullReplication.setAuthenticator(facebookAuthenticator);
pushReplication.setAuthenticator(facebookAuthenticator);
}
return this;
}
public Builder basicAuth(String username, String password) {
Authenticator basicAuthenticator = AuthenticatorFactory.createBasicAuthenticator(username, password);
pullReplication.setAuthenticator(basicAuthenticator);
pushReplication.setAuthenticator(basicAuthenticator);
return this;
}
public Builder addChangeListener(Replication.ChangeListener changeListener) {
pullReplication.addChangeListener(changeListener);
pushReplication.addChangeListener(changeListener);
return this;
}
public Synchronize build() {
return new Synchronize(this);
}
}
private Synchronize(Builder builder) {
pullReplication = builder.pullReplication;
pushReplication = builder.pushReplication;
}
public void start() {
pullReplication.start();
pushReplication.start();
}
public void destroyReplications() {
if (pullReplication != null && pushReplication != null) {
pullReplication.stop();
pushReplication.stop();
pullReplication.deleteCookie("SyncGatewaySession");
pushReplication.deleteCookie("SyncGatewaySession");
pullReplication = null;
pushReplication = null;
}
}
}
And I use it like this:
...
public void startReplicationSync(String facebookAccessToken) {
if (sync != null) {
sync.destroyReplications();
}
final String url = BuildConfig.URL_HOST + ":" + BuildConfig.URL_PORT + "/" + DATABASE_NAME;
sync = new Synchronize.Builder(databaseManager.getDatabase(), url, true)
.facebookAuth(facebookAccessToken)
.addChangeListener(getReplicationChangeListener())
.build();
sync.start();
}
...
My sync gateway json config file:
{
"interface":":4984",
"adminInterface":":4985",
"log":["REST"],
"facebook":{
"register" : true
},
"databases":{
"sync_gateway":{
"server":"http://localhost:8091",
"bucket":"sync_gateway",
"users": {
"GUEST": {"disabled": false}
},
"sync":`function(doc) {channel(doc.channels);}`
}
}
}
I also tried with "GUEST": {"disabled": true}, no luck
My problem is that if I do this
pullReplication.setAuthenticator(facebookAuthenticator);
pushReplication.setAuthenticator(facebookAuthenticator);
Nothing will ever get replicated/pulled from the server. However if I don't set an authenticator, everything is pulled. Is it something I am doing wrong? I really need to use the authenticator in order to prevent some documents to not being replicated for non-authenticated users.
Note! The token is good, as if I am looking in the users section of sync gateway admin, I can see the right profile id of the logged in user token I passed to the couchbase facebook authenticator.
In the Sync Gateway config you provided, the Sync Function is function(doc, oldDoc) {channel(doc.channels);} which means that if the document processed by Sync Gateway contains a string(s) under the channels field, the document will be mapped to this/these channel(s). Let's assume the following config file:
{
"log": ["CRUD"],
"databases": {
"db": {
"server": "walrus:",
"users": {
"GUEST": {"disabled": false, "admin_channels": ["*"]}
},
"sync": `
function sync(doc, oldDoc) {
channel(doc.channels);
}
`
}
}
}
If the channels field doesn't exist then the document will be mapped to a channel called undefined. But the GUEST account has access to the * channel (a placeholder to represent all channels). So, all unauthenticated replications will pull all documents. Let's now introduce the facebook login field in the config file. This time, replications authenticated with a facebook token represent a new user which has only access to the ! channel by default (watch this screencast to understand the ! channel, a.k.a the public channel https://www.youtube.com/watch?v=DKmb5mj9pMI). To give a user access to other channels, you must use the access API call in the Sync Function (read more about all Sync Function API calls here).
In the case of facebook authentication, the user's facebook ID is used to represent the user name. Supposing that the document has a property holding the user's facebook ID (user_id: FACEBOOK_ID), you can map the document to a channel and give the user access to it. The new Sync Function would look like this:
function(doc, oldDoc) {
channel(doc._id);
access(doc.user_id, doc._id);
}
You can retrieve the user's facebook ID with the Facebook Android SDK and save on a document field.

Categories

Resources