In the last chapter, we created a live web-based API using an Azure Function App and updated our app to use it instead of static, hardcoded data. When we created the API, we made it available anonymously, which means anyone can access the data within it. Most web-based APIs require some level of authentication. In some cases, it may be an API key you are provided by the API owner. In other cases, you may be required to sign in with credentials, in order to obtain an authorization token. Whether you are provided a key or granted a token through an authorization process, that key or token can then be used to authenticate all access to the API. In this chapter, we are going to update our API to require authorization, and update our app to allow users to sign in to access the API.
In this chapter, we'll cover the following topics:
We'll start by enabling authentication on the Azure Function App we created in the last chapter.
In the previous chapter, we set up a new, live backend using an Azure Function App. The service contains a single table named entry
, which houses all log entries for our TripLog app and makes them available via an entry
endpoint. Currently, the entry
endpoint is available anonymously.
In this section, we'll change the permissions on the Azure Function App to require each request to contain an access token associated with an authenticated user.
There are a couple of approaches you can use to handle identity and authentication in Azure. You can set up the Azure Function App to use Facebook, Twitter, a Microsoft Account, Google, or even Azure Active Directory as a trusted identity provider. You can also create your own custom identity provider if you want to use account data stored in your database, instead of one of the social providers. You can use one of these options or a combination of several of them—they will all provide an access token that can be used by your mobile app to communicate with your API on behalf of your users. In this section, we'll only use one provider, Facebook. If you want to use a different provider you can still follow the steps in this section, since they're the same for all providers.
In order to use a third-party identity provider, you will need to have an app/client ID and app secret. These keys can be obtained directly from the identity provider by setting up an app for OAuth, typically in their developer portal. Once you've obtained the app/client ID and secret, you can configure the authentication settings for the backend service, as shown in the following steps:
Figure 1: Setting up Authentication / Authorization for an Azure Function App (step 1 of 2)
Figure 2: Azure Function App Authentication / Authorization setup (step 2 of 2)
At this point, any attempt to call the API endpoints, as we did in the previous chapter, will result in an unauthorized response. For example, using either a REST console or the command line, issue a GET
request to the API endpoint using the following URL and you should get back a 401
response:
https://<your-function-name>.azurewebsites.net/api/entry
Next, we'll set up Facebook as an identity provider for our Function App so that we can obtain a user-specific access token that can be used in the request header, allowing us to get back a successful response:
Figure 3: Facebook Authentication Settings for an Azure Function App
https://<your-function-name>.azurewebsites.net/.auth/login/<identity-provider>/callback
. Replace <your-function-name>
with the name of your Function App, and replace <identity-provider>
with facebook
, twitter
, microsoftaccount
, google
, or aad
, depending on which identity provider you are using.Setting up an app for OAuth is different for each provider, and the Azure App Service documentation outlines the steps in detail for each, as follows:
Facebook: https://docs.microsoft.com/en-us/azure/app-service/configure-authentication-provider-facebook
Twitter: https://docs.microsoft.com/en-us/azure/app-service/configure-authentication-provider-twitter
Microsoft Account: https://docs.microsoft.com/en-us/azure/app-service/configure-authentication-provider-microsoft
Google: https://docs.microsoft.com/en-us/azure/app-service/configure-authentication-provider-google
Azure Active Directory: https://docs.microsoft.com/en-us/azure/app-service/configure-authentication-provider-aad
For more details on Azure App Service authentication and authorization, visit https://docs.microsoft.com/en-us/azure/app-service/overview-authentication-authorization
Once you have set everything up on the identity provider side and provided the keys in the Azure portal, you can test it out in your internet browser by navigating to https://<your-function-name>.azurewebsites.net/.auth/login/facebook
.
If everything is set up correctly, you should see the login prompt for the identity provider, as shown in the following screenshot:
Figure 4: Facebook log in page for API authorization
If you observe the URL in the browser address bar after authenticating with the identity provider, you should see the redirected URL appended with a token value in the form of a URL-encoded JSON object. We can then take the value of the authenticationToken
key in that JSON object and use it in a request to our API to confirm that we get back a successful response.
In either a REST console or the command line, issue the same GET
request as we did in the previous section, but this time, add a new header named x-zumo-auth
and use the value from authenticationToken
in the JSON object returned in the redirect URI as the x-zumo-auth
header value:
https://<your-function-name>.azurewebsites.net/tables/entry
--header "x-zumo-auth:<your-authentication-token>"
If everything has been set up correctly, you should get back a response containing all of the Entry
objects in the Azure backend service.
In the next section, we will update the TripLog app with a Facebook authentication page to get an access token that can be stored and used by the app to communicate with the API.
Now that we have enabled our backend service with Facebook authentication, the app as it is from the previous chapter will fail to load content. In this section, we will update the app to authenticate users with Facebook via OAuth and obtain an access token from Azure that can be used in subsequent API calls by the TripLogApiDataService
.
As in the previous chapter, instead of using the identity provider's SDK, we will directly call the API endpoints behind the SDK, to better understand the approach to authenticate to an API in a more generic way. In order to do this, we first make an OAuth call to Facebook, obtaining a Facebook token. We then pass that token to an Azure App Service authentication endpoint, where it is validated using the Facebook app ID and the secret that was added to the service's configuration in Azure, to finally receive the access token needed to make calls to the Function App endpoints.
Performing OAuth in a mobile app requires a certain set of platform-specific capabilities. Fortunately, Xamarin has abstracted this into a cross-platform library, available as a NuGet package, called Xamarin.Auth. We will use the Xamarin.Auth library to perform OAuth in our app. However, we do not want to put this particular implementation detail directly in a ViewModel, because it puts an external dependency on the ViewModel, making it less testable, as discussed in Chapter 4, Platform-Specific Services and Dependency Injection. So, instead of a ViewModel calling the Xamarin.Auth library directly, we will create and use an authentication service, following the inversion of control pattern introduced in Chapter 4, Platform-Specific Services and Dependency Injection. We will start by creating an authentication service interface, as shown in the following steps:
IAuthService
in the Services
folder in the core library:public interface IAuthService
{
}
IAuthService
interface with a single method that takes in all the key components of a standard OAuth call as its parameters:public interface IAuthService
{
void SignInAsync(string clientId,
Uri authUrl,
Uri callbackUrl,
Action<string> tokenCallback,
Action<string> errorCallback);
}
The two callback Action
parameters provide a way to handle both success and failure OAuth responses.
Next, we'll need to create an implementation of this interface that will leverage the Xamarin.Auth library to perform the actual OAuth prompts and requests, as shown in the following steps:
// in iOS AppDelegate
global::Xamarin.Forms.Forms.Init();
global::Xamarin.Auth.Presenters.XamarinIOS.AuthenticationConfiguration.Init();
Xamarin.FormsMaps.Init();
LoadApplication(new App(new TripLogPlatformModule()));
// in Android MainActivity
global::Xamarin.Forms.Forms.Init(this, savedInstanceState);
global::Xamarin.Auth.Presenters.XamarinAndroid.AuthenticationConfiguration.Init(this, savedInstanceState);
Xamarin.FormsMaps.Init(this, savedInstanceState);
LoadApplication(new App(new TripLogPlatformModule()));
IAuthService
, named AuthService
, in the Services
folder in the core library project:public class AuthService : IAuthService
{
}
SignInAsync
method from IAuthService
:public class AuthService : IAuthService
{
public void SignInAsync(string clientId,
Uri authUrl,
Uri callbackUrl,
Action<string> tokenCallback,
Action<string> errorCallback)
{
var presenter = new OAuthLoginPresenter();
var authenticator = new OAuth2Authenticator(clientId, "", authUrl, callbackUrl);
authenticator.Completed += (sender, args) =>
{
if (args.Account != null && args.IsAuthenticated)
{
tokenCallback?.Invoke(args.Account.Properties["access_token"]);
}
else
{
errorCallback?.Invoke("Not authenticated");
}
};
authenticator.Error += (sender, args) =>
{
errorCallback?.Invoke(args.Message);
};
presenter.Login(authenticator);
}
}
TripLogCoreModule
Ninject module in the core library to register the IAuthService
implementation in the IoC:public class TripLogCoreModule : NinjectModule
{
public override void Load()
{
// ...
Bind<IAuthService>().To<AuthService>().InSingletonScope();
}
}
The IAuthService
interface provides a way to perform OAuth against Facebook, which gives us a Facebook authentication token, but we still need a way to pass that Facebook-specific token to our API, to get back an Azure-authenticated access token that we can use in our API requests. Azure App Service authentication provides an endpoint that takes an identity provider-specific token, and in return, provides an Azure-specific access token. In order to use this endpoint, we just need to update our TripLog data service with a new method, as follows:
TripLogApiAuthToken
. As we saw in the preceding section, the response from the /.auth/login/facebook
endpoint is a JSON object, containing a userId
object and an authenticationToken
object; so, this TripLogApiAuthToken
model will represent that structure, so that we can deserialize the response and use the access token for future calls to the TripLog backend service:public class TripLogApiUser
{
public string UserId { get; set; }
}
public class TripLogApiAuthToken
{
public TripLogApiUser User { get; set; }
public string AuthenticationToken { get; set; }
}
ITripLogDataService
interface named AuthenticateAsync
:public interface ITripLogDataService
{
Task AuthenticateAsync(string idProvider, string idProviderToken);
Task<IList<TripLogEntry>> GetEntriesAsync();
Task<TripLogEntry> AddEntryAsync(TripLogEntry entry);
}
Notice the idProvider
parameter, which allows this method to be used for Azure identity providers other than just Facebook.
Action<string>
property to the ITripLogDataService
interface named AuthorizedDelegate
. This delegate will allow callers to take action whenever the app has been authorized to access the data service:public interface ITripLogDataService
{
Action<string> AuthorizedDelegate { get; set; }
Task AuthenticateAsync(string idProvider, string idProviderToken);
Task<IList<TripLogEntry>> GetEntriesAsync();
Task<TripLogEntry> AddEntryAsync(TripLogEntry entry);
}
TripLogApiDataService
to include the AuthorizedDelegate
property and the implementation of the AuthenticateAsync
method that we just added to the ITripLogDataService
. The method needs to make a POST
call to the /.auth/login/facebook
endpoint, with the access token received from the OAuth response in the request body. The service endpoint expects the token in the body to be associated with a key named access_token
. Since our base HTTP service handles serializing the message body data for us, we can simply create a struct
to house the token that will be passed to the endpoint:public class TripLogApiDataService : BaseHttpService, ITripLogDataService
{
readonly Uri _baseUri;
readonly IDictionary<string, string> _headers;
public Action<string> AuthorizedDelegate { get; set; }
struct IdProviderToken
{
[JsonProperty("access_token")]
public string AccessToken { get; set; }
}
public async Task AuthenticateAsync(string idProvider, string idProviderToken)
{
var token = new IdProviderToken
{
AccessToken = idProviderToken
};
var url = new Uri(_baseUri, string.Format(".auth/login/{0}", idProvider));
var response = await SendRequestAsync<TripLogApiAuthToken>(url, HttpMethod.Post, requestData: token);
if (!string.IsNullOrWhiteSpace(response?.AuthenticationToken))
{
var authToken = response.AuthenticationToken;
// Update this service with the new auth token
_headers["x-zumo-auth"] = authToken;
AuthorizedDelegate?.Invoke(authToken);
}
}
// ...
}
Notice how the AuthorizedDelegate
is invoked (if defined) when an auth token is successfully received from the API. This allows other areas of the app to take action when the authentication has successfully completed, as we will see later in this chapter.
TripLogApiDataService
constructor with a string parameter named authToken
. In the AuthenticateAsync
method, we update the _headers
property with the token we received from the backend. However, we also need to be able to set the _headers
property from the constructor, so that we can initialize the service with a token if one already exists (for instance, if a token was persisted in the app's settings after signing in), as shown in the following code:public TripLogApiDataService(Uri baseUri, string authToken)
{
_baseUri = baseUri;
_headers = new Dictionary<string, string>();
_headers.Add("x-zumo-auth", authToken);
}
Now that we've created an authentication service, and have the ability to authorize access to our backend service, we need to update our app to leverage it. In the next section, we'll create a new page and ViewModel, which uses our authentication service to allow users to sign in and access the data from our API.
In order to add sign-in capabilities to our app, we need to create a new Page and a new ViewModel. The ViewModel will be pretty straightforward, containing just a single command that handles signing into Facebook via the IAuthService
interface, and passing the received Facebook token to the Azure backend service through the ITripLogDataService
, as shown in the following steps:
BaseViewModel
, named SignInViewModel
, in the ViewModels
folder in the core library project:public class SignInViewModel : BaseViewModel
{
}
SignInViewModel
with a constructor that takes in INavService
, IAuthService
, and ITripLogDataService
parameters:public class SignInViewModel : BaseViewModel
{
readonly IAuthService _authService;
readonly ITripLogDataService _tripLogService;
public SignInViewModel(INavService navService,
IAuthService authService,
ITripLogDataService tripLogService)
:base(navService)
{
_authService = authService;
_tripLogService = tripLogService;
}
}
Command
property named SignInCommand
to the SignInViewModel
along with its execute Action
:public class SignInViewModel : BaseViewModel
{
// ...
Command _signInCommand;
public Command SignInCommand =>
_signInCommand ?? (_signInCommand = new Command(SignIn));
void SignIn()
{
// TODO: Update with your Facebook App Id and Function App name
_authService.SignInAsync("YOUR_FACEBOOK_APPID",
new Uri("https://m.facebook.com/dialog/oauth"),
new Uri("https://<your-function-name>.azurewebsites.net/.auth/login/facebook/callback"),
tokenCallback: async token =>
{
// Use Facebook token to get Azure auth token
await _tripLogService.AuthenticateAsync("facebook", token);
},
errorCallback: e =>
{
// TODO: Handle invalid authentication here
});
}
}
TripLogCoreModule
Ninject module to add SignInViewModel
to the IoC container:public class TripLogCoreModule : NinjectModule
{
public override void Load()
{
// ViewModels
Bind<SignInViewModel>().ToSelf();
Bind<MainViewModel>().ToSelf();
Bind<DetailViewModel>().ToSelf();
Bind<NewEntryViewModel>().ToSelf();
// ...
}
}
TripLogCoreModule
to account for the updated TripLogApiDataService
constructor and pass in the auth token stored in local settings:public class TripLogCoreModule : Ninject.Modules.NinjectModule
{
public override void Load()
{
// ...
var apiAuthToken = Preferences.Get("apitoken", "");
var tripLogService = new TripLogApiDataService(new Uri("https://<your-function-name>.azurewebsites.net"), apiAuthToken);
// ...
}
}
We now have a ViewModel that handles the authentication flow for our app. Next, we will create the actual sign-in page, which will use the SignInViewModel
as its data context:
Views
folder in the core library named SignInPage
.SignInPage
to add a button that is bound to the SignInCommand
of SignInViewModel
:<ContentPage.Content>
<Button Text="Sign in with Facebook"
Command="{Binding SignInCommand}"
BackgroundColor="#455c9f"
TextColor="White"
Margin="20"
VerticalOptions="Center" />
</ContentPage.Content>
SignInPage
and SignInViewModel
mappings in the navigation service in the TripLogNavModule
Ninject module:public class TripLogNavModule : NinjectModule
{
// ...
public override void Load()
{
var navService = new XamarinFormsNavService();
// Register view mappings
navService.RegisterViewMapping(typeof(SignInViewModel), typeof(SignInPage));
navService.RegisterViewMapping(typeof(MainViewModel), typeof(MainPage));
navService.RegisterViewMapping(typeof(DetailViewModel), typeof(DetailPage));
navService.RegisterViewMapping(typeof(NewEntryViewModel), typeof(NewEntryPage));
Bind<INavService>()
.ToMethod(x => navService)
.InSingletonScope();
}
}
Now that we've created a sign-in page, we need to make a few minor adjustments to the app so that users will go directly to the SignInPage
if an auth token does not exist in local settings, and then go to the MainPage
after successfully signing in.
There are a couple of ways to tap into the platform-specific APIs to store and retrieve local settings. One way is to roll your own service, similar to the way we did with the geolocation service: creating a core interface that is implemented uniquely per platform. Another alternative is to leverage a plugin, or other third-party library, that has already been created and published.
In this section, we'll use the Preferences API from the Xamarin.Essentials library to get and retrieve the API auth token:
bool
property to the App
class in App.xaml.cs
that indicates whether an auth token is present by checking the Xamarin.Essentials Preferences API:public partial class App : Application
{
bool IsSignedIn => !string.IsNullOrWhiteSpace(Preferences.Get("apitoken", ""));
// ...
}
SetMainPage
method in the App
class to set MainPage
to the SignInPage
if IsSignedIn
is false
:public partial class App : Application
{
// ...
void SetMainPage()
{
var mainPage = IsSignedIn
? new NavigationPage(new MainPage())
{
BindingContext = Kernel.Get<MainViewModel>()
}
: new NavigationPage(new SignInPage())
{
BindingContext = Kernel.Get<SignInViewModel>()
};
var navService = Kernel.Get<INavService>() as XamarinFormsNavService;
navService.XamarinFormsNav = mainPage.Navigation;
MainPage = mainPage;
}
// ...
}
AuthorizedDelegate
to a method that saves the token returned from the successful API authorization and then resets the app's MainPage
property:public partial class App : Application
{
public App(params INinjectModule[] platformModules)
{
// ...
Kernel.Load(platformModules);
// Setup data service authentication delegates
var dataService = Kernel.Get<ITripLogDataService>();
dataService.AuthorizedDelegate = OnSignIn;
SetMainPage();
}
void SetMainPage()
{
// ...
}
void OnSignIn(string accessToken)
{
Preferences.Set("apitoken", accessToken);
SetMainPage();
}
// ...
}
Now, when the app is launched for the first time and an auth token is not present in the local settings, you will see the sign-in page. Clicking on the sign-in button will launch the Xamarin.Auth dialog, prompting for Facebook credentials and permission to grant access to the TripLog app, as shown in the following screenshots:
Figure 5: The sign in and authorization pages
Upon successfully authenticating with Facebook, you should be automatically brought to the MainPage
, and the list of the Entry objects will be loaded from the API. In the next section, we'll add the ability for users to sign out of the app.
Now that we've added a sign-in feature to our app, we need to give our users a way to sign out as well. Most apps that deal with authenticating users will put a sign-out button somewhere in an account settings or profile screen. Since our app does not have an account settings or profile screen, we'll simply add a Sign out button to the navigation bar on the main page. In addition to allowing the user to sign out, the app should also automatically sign the user out if it receives any 401
(unauthorized) responses from the API.
We'll add support for automatic sign-out first, because we'll be able to repurpose it when we add the sign-out button:
Action
property to the ITripLogDataService
interface named UnauthorizedDelegate
. This delegate will work like the AuthorizedDelegate
we added earlier in the chapter, except this one will allow callers to take action whenever the app's access to the data service is unauthorized:public interface ITripLogDataService
{
Action<string> AuthorizedDelegate { get; set; }
Action UnauthorizedDelegate { get; set; }
Task AuthenticateAsync(string idProvider, string idProviderToken);
Task<IList<TripLogEntry>> GetEntriesAsync();
Task<TripLogEntry> AddEntryAsync(TripLogEntry entry);
}
TripLogApiDataService
to include the UnauthorizedDelegate
property that we just added to ITripLogDataService
:public class TripLogApiDataService : BaseHttpService, ITripLogDataService
{
readonly Uri _baseUri;
readonly IDictionary<string, string> _headers;
public Action<string> AuthorizedDelegate { get; set; }
public Action UnauthorizedDelegate { get; set; }
// ...
}
GetEntriesAsync
and AddEntryAsync
methods to invoke the UnauthorizedDelegate
(if defined) anytime an UnauthorizedAccessException
is caught:public class TripLogApiDataService : BaseHttpService, ITripLogDataService
{
// ...
public async Task<IList<TripLogEntry>> GetEntriesAsync()
{
try
{
var url = new Uri(_baseUri, "/api/entry");
var response = await SendRequestAsync<TripLogEntry[]>(url, HttpMethod.Get, _headers);
return response;
}
catch (UnauthorizedAccessException)
{
UnauthorizedDelegate?.Invoke();
throw;
}
}
public async Task<TripLogEntry> AddEntryAsync(TripLogEntry entry)
{
try
{
var url = new Uri(_baseUri, "/api/entry");
var response = await SendRequestAsync<TripLogEntry>(url, HttpMethod.Post, _headers, entry);
return response;
}
catch (UnauthorizedAccessException)
{
UnauthorizedDelegate?.Invoke();
throw;
}
}
}
BaseHttpService
SendRequestAsync
method to throw a new UnauthorizedAccessException
if the API response's HTTP status code is a 401
:public abstract class BaseHttpService
{
protected async Task<T> SendRequestAsync<T>(Uri url, HttpMethod httpMethod = null, IDictionary<string, string> headers = null, object requestData = null)
{
// ...
using (var response = await client.SendAsync(request, HttpCompletionOption.ResponseContentRead))
{
var content = response.Content == null
? null
: await response.Content.ReadAsStringAsync();
if (response.IsSuccessStatusCode)
{
result = JsonConvert.DeserializeObject<T>(content);
}
else
{
if (response.StatusCode == HttpStatusCode.Unauthorized)
{
throw new UnauthorizedAccessException();
}
}
}
// ...
}
}
App
class, set the data service's UnauthorizedDelegate
to a method that removes the stored token and then resets the app's MainPage
property:public partial class App : Application
{
public App(params INinjectModule[] platformModules)
{
// ...
Kernel.Load(platformModules);
// Setup data service authentication delegates
var dataService = Kernel.Get<ITripLogDataService>();
dataService.AuthorizedDelegate = OnSignIn;
dataService.UnauthorizedDelegate = SignOut;
SetMainPage();
}
void SetMainPage()
{
// ...
}
void OnSignIn(string accessToken)
{
// ...
}
void SignOut()
{
Preferences.Remove("apitoken");
SetMainPage();
}
// ...
}
Now, anytime the app receives a 401
response from the API, it will automatically sign the user out, and return them to the SignInPage
. Next, we'll add the ability for the user to sign out on their own:
ITropLogDataService
, named Unauthenticate
:public interface ITripLogDataService
{
Action<string> AuthorizedDelegate { get; set; }
Action UnauthorizedDelegate { get; set; }
Task AuthenticateAsync(string idProvider, string idProviderToken);
void Unauthenticate();
Task<IList<TripLogEntry>> GetEntriesAsync();
Task<TripLogEntry> AddEntryAsync(TripLogEntry entry);
}
TripLogDataService
to include the implementation for the Unauthenticate
method we just added to the ITripLogDataService
interface, which simply invokes the UnauthorizedDelegate
:public class TripLogApiDataService : BaseHttpService, ITripLogDataService
{
// ...
public Action<string> AuthorizedDelegate { get; set; }
public Action UnauthorizedDelegate { get; set; }
// ...
public void Unauthenticate() => UnauthorizedDelegate?.Invoke();
// ...
}
Now anytime the user wants to sign out we can call this Unauthenticate
method, which will invoke the UnauthorizedDelegate
, and therefore execute the same code as when a 401
is received and we automatically sign the user out. Now all we need to do is add a sign-out button that calls this Unauthenticate
method.
As with the other buttons or components in the user interface, the sign-out button should be data bound to a ViewModel Command
property, so, we'll start by adding that:
Command
property named SignOutCommand
to MainViewModel
that simply calls the Unauthenticate
method in the data service:public class MainViewModel : BaseViewModel
{
readonly ITripLogDataService _tripLogService;
// ...
public Command SignOutCommand => new Command(_tripLogService.Unauthenticate);
// ...
}
MainPage
navigation bar that is bound to the SignOutCommand
we just added:<ContentPage ... >
<ContentPage.ToolbarItems>
<ToolbarItem Text="New" Command="{Binding NewCommand}" />
<ToolbarItem Text="Sign out" Command="{Binding SignOutCommand}" />
</ContentPage.ToolbarItems>
<ContentPage.Content>
<!-- ... -->
</ContentPage.Content>
</ContentPage>
Now, when the app is run, there'll be a Sign out button on the navigation bar of its main page, as shown in the following screenshot. When the user clicks on the Sign out button, the stored auth token will be cleared and they will be routed back to the sign-in page.
Figure 6: The TripLog main page with a Sign Out button
In this chapter, we updated the Azure backend service we created in the previous chapter with Facebook-provided authentication. We also updated the API data service in the TripLog app, to authenticate its HTTP API requests with a user-specific auth token provided by Azure App Service authentication, given a valid Facebook access token. Finally, we added a sign-in page, the ability to sign out, and updated the app to automatically route the user to the sign-in page if an auth token isn't found in local settings, or if a 401
response is ever received from the API. In the next chapter, we'll create unit tests for our TripLog app.