We previously looked how to create a Xamarin Forms App using Azure Mobile Services for storing and retrieving data. However, up until now, we have been saving the data globally so that it is accessible to all users. This post will explore how to add device-specific authentication to each app so that users can register and manage their own private data using any of the supported authentication providers.
Azure Identity Providers
Azure Mobile Services includes built-in support for authenticating against several different providers, including Facebook, Twitter, Microsoft, Google and even its own Azure Active Directory. Setting these up is as simple as creating the respective application and entering the credentials in the Identity tab for your Mobile Service: For complete details and a thorough walkthrough the process of setting up each provider, be sure to check out this helpful article: Get started with authentication in Mobile Services. Before we can proceed to add the authentication screens to the application, we first need to make sure that our app is updated to only show the data for the currently authenticated user.
Filtering Data for Authenticated Users
While it is possible to issue a query on the client to only retrieve the user's data by UserId, Azure Mobile Services exposes a more central location to filter the data in the Script tab of the data tables. By extending the existing query object with where method, we can filter against the user object, which is also sent with the request, automatically populated with the user credentials from the Azure Mobile Client: Of course, we need to make sure to update the Store model to include this property, as well as our Insert methods to ensure that the UserId included in the model when updating. For more, see the complete source code below for the full implementation and changes.
Creating a Cross Platform Login View
Since we're supporting all four social platforms, I've created a simple view which displays each as a button, the Command of which is linked to the ViewModel to execute the login on each device. Up until now we have been able to put virtually all of our code, views, models, and viewmodels in the main shared project, delegating only the actual launching of the app to the individual platforms. However, supporting a login screen across the different platforms requires a little extra work.. The reason for this is that each implementation of the Login requires that it be launched within a UI element, which obviously is specific to the individual platform. Indeed the Login screens from the Azure Mobile Client are implemented separately within each platform within the Microsoft.WindowsAzure.Mobile.Ext extensions library, and are unavailable in the shared project. After researching this extensively, I encountered various strategies for handling this, such as creating a separate client class implementation within each platform and injecting it to the shared project when launching the app from each platform, or making use of a custom renderer. However, before I could explore either one of these options, I discovered that the folks at Xamarin already created simple solution to this problem by exposing a very simple Dependency Injection service which allows you to easily execute device-specific behavior from the shared project code.
Xamarin Forms DependencyService
The Xamarin Forms DependencyService is a simple dependency resolver that makes it easy to grab the platform-specific implementation of a feature at runtime. This allows you to define the device-specific code within each platform, while abstracting the behavior in the shared project to keep the code reusable. To use this feature you want to complete the following:
1. Define the Interface in the Shared Project
The Interface is the abstract definition which exposes the methods that should be called within the individual platform implementations. For this example, I've created a simple IMobileClient interface which receives the provider type to issue the authentication.
public interface IMobileClient { Task<MobileServiceUser> Authorize(MobileServiceAuthenticationProvider provider); }
2. Implement the Platform-Specific Behavior in Each Project
Each project now needs an implementation of the Login method so that it can be called and execute within its own UI context. It’s important to note that each implementation includes a parameter-less constructor, which is required by the DependencyService. Here's the Android implementation of the method:
public class AndroidLoginClient : IMobileClient {
public AndroidLoginClient() { }
public async Task<MobileServiceUser> Authorize(MobileServiceAuthenticationProvider provider) {
return await App.Client.LoginAsync(Xamarin.Forms.Forms.Context, provider);
}
}
Using the static reference to the Context we can pass in the required container in which to render the login. However, I was unable to locate the equivalent UIViewController for iOS. Instead, I declared a static UIViewController property on the AppDelegate class so that I can reference it in the Login method as needed to render the Login screen.
[Register("AppDelegate")] public partial class AppDelegate : UIApplicationDelegate {
// ...
public static UIViewController MainView; p
ublic override bool FinishedLaunching(UIApplication app, NSDictionary options) {
// ...
MainView = App.GetMainPage().CreateViewController();
// ...
} }
Now we can simply refer to that from the iOS implementation to render the login screen:
public class iOSMobileClient : IMobileClient {
public async Task<MobileServiceUser> Authorize(Microsoft.WindowsAzure.MobileServices.MobileServiceAuthenticationProvider provider) {
return await App.Client.LoginAsync(AppDelegate.MainView, provider);
}
}
Finally, because Windows Phone launches an entirely separate View to display the login screen, we can simply call the LoginAsync extension method for the platform directly, ignoring the container.
public class WinPhoneLoginClient : IMobileClient {
public async Task<MobileServiceUser> Authorize(MobileServiceAuthenticationProvider provider) {
return await RebateReminder.App.Client.LoginAsync(provider);
}
}
3. Add Dependency Attributes
The final step in declaring the separate implementations is to mark each platform definition with the Xamarin.Forms.Dependency attribute within each class. Take note that this attribute needs to be declared outside any enclosing namespaces for the class (but after the using statements). Here's an example of the Windows Phone attribute declaration:
[assembly: Xamarin.Forms.Dependency (typeof (WinPhoneLoginClient))]
You can see the remaining attributes in place in the final source code available below.
4. Call DependencyService.Get
Now that the individual implementations are defined, we simply need to call the generic static method DependencyService.Get<> passing in our interface IMobileClient as the type. Xamarin will automatically resolve the correct implementation at runtime, executing the appropriate Login method for each device. Here's the code from the ViewModel that executes the login:
private async Task ExecuteLoginCommand(string service) {
if (IsLoading || string.IsNullOrEmpty(service)) return;
MobileServiceAuthenticationProvider provider;
switch (service) {
case "Facebook": provider = MobileServiceAuthenticationProvider.Facebook;
break;
case "Twitter": provider = MobileServiceAuthenticationProvider.Twitter;
break;
case "Microsoft": provider = MobileServiceAuthenticationProvider.MicrosoftAccount;
break;
case "Google": provider = MobileServiceAuthenticationProvider.Google;
break;
default: throw new ArgumentOutOfRangeException(service);
}
IsLoading = true;
try {
//await App.Platform.Authorize(container, provider);
var user = await DependencyService.Get<IMobileClient>().Authorize(container, provider);
MessagingCenter.Send(Client, "LoggedIn");
} catch (InvalidOperationException ex) {
if (ex.Message.Contains("Authentication was cancelled by the user")) { }
} catch (Exception ex) {
var page = new ContentPage(); page.DisplayAlert("Error", "Error logging in. Please check connectivity and try again.", "OK", null);
}
IsLoading = false;
}
We pass the desired service to the implementation, and use the MessagingCenter to notify the Welcome View that the authentication is complete so that the main Stores view can be displayed. See the source code below for the full implementation details. Clicking the login button for each identity service will display the platform-specific login screen on each device, as demonstrated for facebook here: And as expected, logging in with the same identity on the different devices reveals the same data across all three platforms: Navigating back to the Welcome View reveals a Logout button, which will clear out the CurrentUser of the client and reset to the original Welcome view. However, there is one minor issue here, which is that if previously checked the "Remember me" option for Facebook, and click to use that provider, the login screen will be bypassed and you'll automatically be logged in. This may or may not be the desired behavior, so let's quickly see how we can ensure a full logout if necessary.
Logging the User Out of Azure Mobile Services
Because the Login screen for each identity provider actually executes in a Web browser, the cookies for the application remain with the application even after logging out of the client. As a result, will automatically sign in the user if they selected the option to remember the login. If you want to be able to fully logout the user, you can extend the existing IMobileClient interface with a custom Logout function so that the platform-specific task of clearing out all the cookies can be executed as needed on each device. For iOS, the cookies are accessible via NSHttpCookieStorage.SharedStorage.Cookies. For Android you want to make use of the Android.Webkit.CookieManager. Finally for Windows Phone, you create a temporary WebBrowser control and clear the cookies using the ClearCookiesAsync extension method. The complete implementation for Logout is available in the source code below. I've tested this on all three platforms and it appears to work as expected, but if anyone knows a different/better way to handle this, please feel free to sound off in the comments!
Wrapping Up and Next Steps
By combining the Identity features of Azure Mobile Services together with the cross-platform client and device-specific extensions, we were able to target all three platforms with minimal branching of code. Using the DependencyService we can resolve the platform-specific code at runtime, maximizing code reuse.
Get the CODE!
We've come a long way in creating a truly cross-platform, mobile-connected application! But we still have a few features left to tackle. In a future post we'll look at how we can leverage the built-in Push Notification services of Azure to (at long last) fulfill the "Reminder" portion of the application. We also need to explore how we can leverage the Offline services to handle disconnected scenarios, and much much more! Stay tuned! In the meantime, if there's anything you'd like to see explored further, or any new features that you'd like to see implemented in the app, please let me know in the comments, and as always, I hope this was helpful!