mvvmcross and authentication - mvvmcross

Is there a way to authenticate a user through Facebook in mvvmcross framework? I'm currently attempting use Mobile Azure Service to authenticate to Facebook, but I don't have any success. Without using mvvmcross, I can authenticate just fine.
Thank You!
Mark

In the MVVM sense what I've found is that no, you can not. Properties on the facebook login page are not bindable, nor should they be and is best treated as a modal view out of your control
What I would do is make it a view concern and use Xamarin.Auth to authenticate.
As an example let's say that you had a LoginView and LoginViewModel.
The LoginView provides your standard login Email/Password but with an option (button) to authenticate via facebook
From that view hook up to the touchupinside event of the facebooklogin button
this.btnFacebookLogin.TouchUpInside += (object sender, EventArgs e) =>
{
DoFacebookLogin ();
}
Then in your DoFacebookLogin method 'present' the viewcontroller for facebook as described here https://github.com/xamarin/Xamarin.Auth/blob/master/GettingStarted.md
For example :
private void DoFacebookLogin ()
{
var auth = new OAuth2Authenticator (
clientId: "yournumericclientidhere",
scope: "",
authorizeUrl: new Uri ("https://m.facebook.com/dialog/oauth/"),
redirectUrl: new Uri ("http://www.facebook.com/connect/login_success.html"));
auth.AllowCancel = true;
auth.Completed += (sender, eventArgs) => {
DismissViewController (false, null);
if (eventArgs.IsAuthenticated) {
string user = eventArgs.Account.Serialize ();
var messenger = Mvx.Resolve<IMvxMessenger> ();
messenger.Publish (new FacebookLoggedIn (user));
} else {
// Cancelled here
}
};
var vc = auth.GetUI ();
this.PresentViewController (vc, true, null);
}
Cancelled does not need to be handled since the modal viewcontroller will take you back to your LoginView
On success that viewcontroller is dismissed then I would use mvx's interpretation of an eventaggregator (plugins.messenger) to send a message to the viewmodel that the facebook modal view is closed, with that message you can pass the account details - accesstoken etc back to the viewmodel to do as you wish.
View (as above) :
string user = eventArgs.Account.Serialize();
var messenger = Mvx.Resolve<IMvxMessenger> ();
messenger.Publish (new FacebookLoggedIn (user));
Message Class in your PCL :
public class FacebookLoggedIn : MvxMessage
{
public FacebookLoggedIn(object sender) : base(sender) {}
}
ViewModel also in your PCL :
public LoginViewModel()
{
var messenger = Mvx.Resolve<IMvxMessenger> ();
user = messenger.SubscribeOnMainThread<FacebookLoggedIn> (OnFacebookLoggedIn);
}
private void OnFacebookLoggedIn (FacebookLoggedIn MvxMessage)
{
... do something with the accesstoken? call your IUserService etc
ShowViewModel<MainViewModel> ();
}
Since you're dismissing the facebook viewcontroller you'll find yourself back on the loginview momentarily before automatically navigating to the MainView
In your view project you need to ensure the plugin is loaded otherwise you'll receive an error during construction of the viewmodel, so in setup.cs
protected override void InitializeLastChance ()
{
Cirrious.MvvmCross.Plugins.Messenger.PluginLoader.Instance.EnsureLoaded();
base.InitializeLastChance ();
}
Additionally you can also store the account credentials locally, this is described on the Xamarin.Auth link under AccountStore.Create().Save. Note that if you receive a platform not supported exception then add PLATFORM_IOS as a preprocessor directive to your project.
I realise the question is a couple of months old but since it rates high on google thought I'd provide an answer since there isn't any

Related

How to Authorize user on winforms when connecting to telegram

If i run this code on console application:
static async Task Main(string[] _)
{
using var client = new WTelegram.Client();
var user = await client.LoginUserIfNeeded();
Console.WriteLine($"We are logged-in as {user.username ?? user.first_name + " " + user.last_name} (id {user.id})");
}
It will prompt interactively for App api_id and api_hash.
How can i Authorize user on winforms application?. So that i can input the api_id and api_hash through textbox
Edit: (Oct 2022) Latest version of the library has a simplified config system that makes it more easy to use in WinForms apps.
Please take a look at the example WinForms app provided in the repository that demonstrate how to proceed.
The original answer below is still valid but maybe more complex
=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
First, you should read WTelegramClient FAQ #3:
3. How to use the library in a WinForms or WPF application
The library should work without a problem in a GUI application.
The difficulty might be in your Config callback when the user must enter the verification code or password, as you can't use Console.ReadLine here.
An easy solution is to call Interaction.InputBox("Enter verification code") instead.
This might require adding a reference (and using) to the Microsoft.VisualBasic assembly.
A more complex solution requires the use of a ManualResetEventSlim that you will wait for in Config callback,
and when the user has provided the verification_code through your GUI, you "set" the event to release your Config callback so it can return the code.
Here is an example solution for your Form class with a ManualResetEventSlim and textboxes:
using Microsoft.VisualBasic;
using TL;
private readonly ManualResetEventSlim _codeReady = new ManualResetEventSlim();
private WTelegram.Client _client;
private User _user;
string Config(string what)
{
switch (what)
{
case "api_id": return textBoxApiID.Text;
case "api_hash": return textBoxApiHash.Text;
case "phone_number": return textBoxPhone.Text;
case "verification_code":
_codeReady.Reset();
_codeReady.Wait();
return textBoxCode.Text;
case "password": return Interaction.InputBox("Enter 2FA password");
default: return null;
};
}
private void textBoxCode_KeyPress(object sender, KeyPressEventArgs e)
{
if (e.KeyChar == '\r') // pressing Return in the textboxCode
{
_codeReady.Set();
e.Handled = true;
}
}
private async void buttonLogin_Click(object sender, EventArgs e)
{
buttonLogin.Enabled = false;
_client = new WTelegram.Client(Config);
_user = await _client.LoginUserIfNeeded();
MessageBox.Show("We are now connected as " + _user);
}
private async void buttonGetChats_Click(object sender, EventArgs e)
{
if (_user == null) { MessageBox.Show("You must complete the login first."); return; }
var chats = await _client.Messages_GetAllChats(null);
MessageBox.Show(string.Join("\n", chats.chats.Values.Where(c => c.IsActive)));
}

How to set up google api in asp.net mvc to access docs

About 6 months ago I set up a web application in the google developers console so that employees of our internal web site could initiate emails which would read a template doc in a google account, merge some fields and then download a pdf version of it to email out.
Now we have to move those template docs to a different google managed domain/user account so I've made copies of the documents in the new account and updated our references with the new doc ids.
In addition, the email I had when I originally created this application in the google dev console is going away as of the first of the year. So I also have to recreate the app under a new account.
I've done that and matched all the settings of the original app. However, when I try to access a document I get the error Google.Apis.Auth.OAuth2.Responses.TokenResponseException: 'Error:"unauthorized_client", Description:"Unauthorized", Uri:""'
I had followed this page in setting up the original user authentication. I know there was a ton of trial and error before I actually got it working and I must be forgetting something. I'm wondering if it's tied to needing to reauthenticate the new app. Although I'm specifying the new clientid and clientsecret from the new app, I don't get the popup asking me to give permission to the app. I would expect with the new credential info that it would open that window asking me to give permission. Here's that file for reference. Any ideas?
public class AuthCallbackController : Google.Apis.Auth.OAuth2.Mvc.Controllers.AuthCallbackController
{
protected override FlowMetadata FlowData => new AppFlowMetadata();
}
public class AppFlowMetadata : FlowMetadata
{
private static readonly IAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow(new GoogleAuthorizationCodeFlow.Initializer
{
ClientSecrets = new ClientSecrets
{
ClientId = AwsSecrets.GoogleCreds.ClientId,
ClientSecret = AwsSecrets.GoogleCreds.ClientSecret
},
Scopes = new[] {DriveService.Scope.Drive},
DataStore = new FileDataStore("Drive.Api.Auth.Store")
});
public override IAuthorizationCodeFlow Flow => flow;
public override string GetUserId(Controller controller)
{
return "userid";
}
}
public class GoogleController : TECWareControllerBase
{
private readonly IGoogleCredentialService _gservice;
public GoogleController(IGoogleCredentialService gservice)
{
_gservice = gservice;
}
public async Task<ActionResult> IndexAsync(CancellationToken cancellationToken)
{
var result = await new AuthorizationCodeMvcApp(this, new AppFlowMetadata()).AuthorizeAsync(cancellationToken);
if (result.Credential != null)
{
_gservice.SaveRefreshToken(result.Credential.Token.RefreshToken);
return View();
}
return new RedirectResult(result.RedirectUri);
}
}
I finally found a way to get this working.
First off in this method
private static readonly IAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow(new GoogleAuthorizationCodeFlow.Initializer
{
ClientSecrets = new ClientSecrets
{
ClientId = AwsSecrets.GoogleCreds.ClientId,
ClientSecret = AwsSecrets.GoogleCreds.ClientSecret
},
Scopes = new[] {DriveService.Scope.Drive},
DataStore = new FileDataStore("Drive.Api.Auth.Store")
});
I had to change the FileDataStore("Drive.Api.Auth.Store") key to something else like FileDataStore("GoogleAuth")
That forced the authentication to fire up.
Unfortunately, google then complained about an invalid redirect uri. The following code returned a redirect uri of http://localhost:11224/AuthCallback/IndexAsync which didn't even exist in my web application's Authorized redirect uris. It should have been http://localhost:11224/MVC/AuthCallback/IndexAsync. So in the url result's redirect url I changed it to what it should have been which allowed me to complete the authorization. Now I can access the documents in the authenticated account.
var result = await new AuthorizationCodeMvcApp(this, new AppFlowMetadata()).AuthorizeAsync(cancellationToken);

Adding "rate my app" to Web App Template

There's a project called Web App Template (aka WAT - http://wat.codeplex.com/) that allows you to wrap a webapp as a Windows 8 / Windows Phone 8 application. I've done that to an app, now I'm trying to add the "rate my app" feature to it. I don't see where/if I can inject code for this component to be added.
I'm following a guide here: http://developer.nokia.com/community/wiki/Implement_%22Rate_My_App%22_in_under_60_seconds
I'm stuck at Step 5 - where do I add the Event Handler? There is no MainPage.xaml.cs and I don't see any similar files.
I imagine that WAT is calling another library to load a web browser. Is there some way I can inject an Event Handler and method into this library?
I suggest not to prompt the user with 'rate my app' thing in the first opening of the app as user should be given some time to see what the app looks like and how it functions. Therefore, keeping the number of app launches and asking to rate the app after some 5th - 10th launch of app will make more sense. Besides you should check if you already prompted the user to rate your app, if so never prompt again. (Otherwise you will piss them off with 'rate my app' thing)
In order to achieve this, you should at first keep the app launch count in app settings class.
The interface for storing any kind of setting:
public interface ISettingService
{
void Save();
void Save(string key, object value);
bool AddOrUpdateValue(string Key, object value);
bool IsExist(string key);
T Load<T>(string key);
T GetValueOrDefault<T>(string Key, T defaultValue);
}
The rating service class that consumes the above interface to store such count and settings:
public class RatingService
{
private const string IsAppRatedKeyName = "isApprated";
private const string TabViewCountKeyName = "tabViewCount";
private const bool IsAppratedDefault = false;
private const int TabViewCountDefault = 0;
private const int ShowRatingInEveryN = 7;
private readonly ISettingService _settingService;
[Dependency]
public RatingService(ISettingService settingService)
{
_settingService = settingService;
}
public void RateApp()
{
if (_settingService.AddOrUpdateValue(IsAppRatedKeyName, true))
_settingService.Save();
}
public bool IsNeedShowMessage()
{
return (_settingService.GetValueOrDefault(TabViewCountKeyName, TabViewCountDefault)%ShowRatingInEveryN) == 0;
}
public void IncreaseTabViewCount()
{
int tabCount = _settingService.GetValueOrDefault(TabViewCountKeyName, TabViewCountDefault);
if (_settingService.AddOrUpdateValue(TabViewCountKeyName, (tabCount + 1)))
_settingService.Save();
}
public bool IsAppRated()
{
return _settingService.GetValueOrDefault(IsAppRatedKeyName, IsAppratedDefault);
}
}
This is how you will run such functionality and prompt the user to rate the app (if previously not rated) anywhere in your project (mainpage or some other page where user launches some functionality):
private void RunRating()
{
if (!RatingService.IsAppRated() && RatingService.IsNeedShowMessage())
{
MessageBoxResult result = MessageBox.Show("Review the app?", "Would you like to review this awesome app?",
MessageBoxButton.OKCancel);
//show message.
if (result == MessageBoxResult.OK)
{
RatingService.RateApp();
new MarketplaceReviewTask().Show();
}
}
}

Windows Phone 8 SDK - Issue with screen locking, and application starting over

I have an application with a webbrowser control in it. When I navigate in that control then step away for a bit, then come back to it (after unlocking the screen due to inactivity), the first/original page shows up again. How can I maintain the state of the browser?
Define a public property Url in App.xaml.cs to store an Url
public Uri Url { get; set; }
On WebBrowser_LoadCompleted event: save WebBrowser.Source property which contains the current loaded Url to above Url property of Application class.
App app = Application.Current as App;
app.Url = WebBrowser.Source;
On Application_Deactivated event (send app to background), save current app's state to IsolatedStorage
IsolatedStorageSettings settings = IsolatedStorageSettings.ApplicationSettings;
settings["Url"] = Url;
settings.Save();
On Application_Launching event (resume app), pull the stored data back
IsolatedStorageSettings settings = IsolatedStorageSettings.ApplicationSettings;
Url currentUrl;
if (settings.TryGetValue("Url", out currentUrl))
Url = (Uri)settings["Url"];
Then from the restored Url, you can re-load the last navigated page.
App app = Application.Current as App;
WebBrowser.Navigate(app.Url);
You can try this:
bool isNew = true;
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
isNew = false;
}
this property will be maintained until your page is closed, so you can use it to test if this is the first time your page is navigated to.

gwt with RequestFactory, handling server side exception on client and RequestFactoryEditorDriver

i use RequestFactory for communicating with server and RequestFactoryEditorDriver on the client side. So editing workflow looks like such way. Create new proxy for editing:
RequestContext reqCtx = clientFactory.getRequestFactory().Request();
UserAndAccountProxy userAndAccountProxy = reqCtx.create(UserAndAccountProxy.class);
reqCtx.saveAndReturnProfileAndAccount(userAndAccountProxy).to(
new Receiver<UserAndAccountProxy>() {
#Override
public void onSuccess(UserAndAccountProxy response) {
...
}
#Override
public void onFailure(ServerFailure error) {
...
}}
And Save button click handling:
RequestContext reqCtx = view.getEditorDriver().flush();
reqCtx.fire();
On server side saveAndReturnProfileAndAccount method can throw exceptions on persisting, which i can handle in onFailure method. After that if I create new proxy with new request context and pass it to my editor all fields will be blanked.
So what is proper way to execute request and if something goes wrong use data that user allready fill or maybe I made mistake in my editing worklow?
So, I think, I found solution. I made changes to function, which create RequestContext:
private void edit(MyProxy proxy) {
RequestContext reqCtx = clientFactory.getRequestFactory().Request();
if (proxy == null) {
// create proxy first time
proxy = reqCtx.create(UserAndAccountProxy.class);
} else {
// create immutable copy
proxy = reqCtx.edit(proxy);
}
final UserAndAccountProxy tmp = proxy;
reqCtx.saveAndReturnMyProxy(proxy).to(new Receiver<MyProxy>() {
#Override
public void onFailure(ServerFailure error) {
eventBus.showErrorInformation(error.getMessage());
//recursive call with already filled proxy
edit(tmp);
}
#Override
public void onSuccess(UserAndAccountProxy response) {
eventBus.showInformation("It`s ok!");
eventBus.goToMainPage(null);
}
});
// start editing with editor
getView().onEdit(tmp, reqCtx);
}
When we start editing proxy function edit need to bee called with null argument and new clean proxy will be created. After that we start edit it with Editor. On Save button click we execute request to server. If it ends with success - we open another page. If request ends with error, we create new immutable copy ant push it to editor.