Friday, 6 November 2020

Display SVGs as TabbedPage tab icons in Xamarin.Forms

A common question that Xamarin.Forms users ask is “how the hell do I get my TabbedPage to display SVG-based tab icons?” A quick look at the TabbedPage doc reveals a couple of hints:

  • iOS – The TabbedRenderer has an overridable GetIcon method that can be used to load tab icons from a specified source. This override makes it possible to use SVG images as icons on a TabbedPage.
  • Android – The TabbedPageRenderer for Android AppCompat has an overridable SetTabIconImageSource method that can be used to load tab icons from a custom Drawable. This override makes it possible to use SVG images as icons on a TabbedPage.

After a few hours investigation I discovered that while the doc is accurate, the advice for Android is no longer the best approach. Therefore, the purpose of this blog post is to outline the simplest techniques for displaying SVGs as TabbedPage icons on iOS and Android, with Xamarin.Forms.

The sample this code comes from can be found on GitHub.

Implementation

iOS

Xcode 12 introduced SVG image support for iOS (specifically iOS 13 or greater). However, the Xamarin.Forms TabbedPage doesn’t currently support consuming them. Therefore, to display an SVG as a TabbedPage icon it’s necessary to use a third-party library. Enter FFImageLoading. For information about using the SVG support in FFImageLoading, including how to configure your iOS project to use this library, see SVG Support.

The process for displaying SVGs as TabbedPage icons on iOS is as follows:

  1. Add the Xamarin.FFImageLoading.Svg.Forms NuGet package to your iOS project.
  2. Call CachedImageRenderer.Init() in your AppDelegate class.
  3. Add your SVGs to the Resources folder of your iOS project, and set the build action for each SVG to BundleResource.
  4. Write a custom renderer for the TabbedPage, which overrides the GetIcon method to load an SVG file.

The following code shows a custom renderer for TabbedPage, which overrides the GetIcon method:

[assembly: ExportRenderer(typeof(MyTabbedPage), typeof(MyTabbedPageRenderer))]
namespace TabbedPageSVGIcons.iOS
{
    public class MyTabbedPageRenderer : TabbedRenderer
    {
        protected override async Task<Tuple<UIImage, UIImage>> GetIcon(Page page)
        {
            UIImage imageIcon;

            if (page.IconImageSource is FileImageSource fileImage && fileImage.File.Contains(".svg"))
            {
                // Load SVG from file
                UIImage uiImage = await ImageService.Instance.LoadFile(fileImage.File)
                    .WithCustomDataResolver(new SvgDataResolver(15, 15, true))
                    .AsUIImageAsync();
                imageIcon = uiImage.ImageWithRenderingMode(UIImageRenderingMode.AlwaysOriginal);
            }
            else
            {
                // Load Xamarin.Forms supported image
                IImageSourceHandler sourceHandler = null;
                if (page.IconImageSource is UriImageSource)
                    sourceHandler = new ImageLoaderSourceHandler();
                else if (page.IconImageSource is FileImageSource)
                    sourceHandler = new FileImageSourceHandler();
                else if (page.IconImageSource is StreamImageSource)
                    sourceHandler = new StreamImagesourceHandler();
                else if (page.IconImageSource is FontImageSource)
                    sourceHandler = new FontImageSourceHandler();

                UIImage uiImage = await sourceHandler.LoadImageAsync(page.IconImageSource);
                imageIcon = uiImage.ImageWithRenderingMode(UIImageRenderingMode.AlwaysOriginal);
            }

            return new Tuple<UIImage, UIImage>(imageIcon, null);
        }
    }
}

In this example, if the image filename contains .svg, the SVG is loaded by the FFImageLoading library, and returned as a UIImage for display. If the image isn’t an SVG, it’s loaded by Xamarin.Forms with the assistance of some in-built image handlers.

Note that FFImageLoading is actually rasterising the SVG to a UIImage, which raises the question why bother when you could just supply your own raster images? Well, exactly. The only benefit to the approach presented here is that SvgDataResolver type enables you to specify the size of the rasterised image. This enables you to develop logic that returns a specific sized image (based on the device the code is running on) from a single image file.

Android

It used to be necessary to write a custom renderer to display SVGs as TabbedPage icons on Android. However, this is no longer the case.

Android Studio includes a tool called Vector Asset Studio that enables you to import SVG files into your Android project as vector drawable resources. These vector resources can then automatically be displayed as TabbedPage icons, without any additional work. For more information about Vector Asset Studio, see Add multi-density vector graphics.

The process for displaying SVGs as TabbedPage icons on Android is as follows:

  1. Convert your SVGs to vector drawable resource XML files.
  2. Add your vector drawable resource XML files to the Resources/drawable folder of your Android project, and set the build action for each XML file to AndroidResource.

That’s it! There’s literally nothing else to consuming SVGs as TabbedPage icons on Android, via Xamarin.Forms. If you don’t want to install Android Studio to handle the conversion from SVG to vector files, you can use an online tool such as Android SVG to VectorDrawable. While this tool is deprecated, it still works (and there are other online tools if this one becomes unavailable).

Create a TabbedPage

The icon displayed for each tab on a TabbedPage is specified by the IconImageSource property of the page type that represents the tab in the TabbedPage.The following code example shows a TabbedPage containing three ContentPage objects:

<local:MyTabbedPage xmlns="http://xamarin.com/schemas/2014/forms"
                    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
                    xmlns:local="clr-namespace:TabbedPageSVGIcons"
                    xmlns:android="clr-namespace:Xamarin.Forms.PlatformConfiguration.AndroidSpecific;assembly=Xamarin.Forms.Core"
                    x:Class="TabbedPageSVGIcons.MainPage"
                    android:TabbedPage.ToolbarPlacement="Bottom">
    <ContentPage Title="Tab 1"
                 IconImageSource="{OnPlatform iOS=apple.svg, Android=ic_apple}">
        <StackLayout Margin="20">
            <Label Text="Tab 1"
                   HorizontalOptions="Center"
                   VerticalOptions="CenterAndExpand" />
        </StackLayout>
    </ContentPage>
    <ContentPage Title="Tab 2"
                 IconImageSource="{OnPlatform iOS=android.svg, Android=ic_android}">
        <StackLayout Margin="20">
            <Label Text="Tab 2"
                   HorizontalOptions="Center"
                   VerticalOptions="CenterAndExpand" />
        </StackLayout>
    </ContentPage>
    <ContentPage Title="Tab 3"
                 IconImageSource="{OnPlatform iOS=windows.svg, Android=ic_windows}">
        <StackLayout Margin="20">
            <Label Text="Tab 3"
                   HorizontalOptions="Center"
                   VerticalOptions="CenterAndExpand" />
        </StackLayout>
    </ContentPage>
</local:MyTabbedPage>

In this example, each ContentPage object specifies an IconImageSource as a vector file (SVG for iOS, vector drawable resource on Android). The OnPlatform markup extension is used to specify the different resource filenames on iOS and Android.

On iOS, the custom renderer ensures that each SVG is drawn on the TabbedPage tabs:

On Android, the Android runtime ensures that each vector drawable resource is drawn on the TabbedPage tabs by Xamarin.Forms:

It’s also worth pointing out that the vector drawable resources are drawn regardless of whether the TabbedPage toolbar is displayed at the top or bottom of the screen:

Wrapping up

Displaying SVGs as TabbedPage icons on iOS and Android, using Xamarin.Forms, requires two different techniques:

  • On iOS, you write a custom renderer for the TabbedPage, and override the GetIcon method to convert an SVG file to a UIImage.
  • On Android, you convert your SVGs to vector drawable resources, and consume them from Xamarin.Forms as you would any other image resource.

Hopefully this has cleared up how to do this. I’ll get the TabbedPage doc updated to reflect this.

The sample this code comes from can be found on GitHub.

Friday, 28 August 2020

Wavelet-based image upscaling

Last summer I wrote several blog posts about performing cross-platform imaging using Xamarin.Forms and SkiaSharp. My first post talked about accessing image pixel data using SkiaSharp, and performing basic imaging algorithms on that data. My second post talked about using convolution kernels to perform different imaging operations. My third post talked about frequency filtering using a Fast Fourier Transform. I always intended to write a fourth post on performing wavelet transforms, but other work got in the way and I forgot about it.

Many years ago, my life revolved around wavelet transforms for several years. I used to implement them in C++, and despite all kinds of attempts to speed them up they were limited by the then speed of hardware. This week my mind drifted back to wavelet transforms, because my brain was feeling in need of a challenge.

So I decided to take my old C++ wavelet transform code and re-implement it in C#, using SkiaSharp to get image pixel data in a Xamarin.Forms app. The initial problem was finding an application for wavelet transforms. Traditionally they've been used for compression, as wavelet transforms are fantastic for decomposing data into its different frequency components, with different levels of resolution. However, coding compression algorithms is way beyond what I had in mind. So I started to think about what else they could be used for.

I had a eureka moment when I realised they should be great for upscaling images. A quick Google search revealed that academia had realised this before me, and there were lots of research papers in this area. Regardless, I decided to to implement wavelet-based image upscaling.

I'm not going to explain what a wavelet transform is. Wikipedia, and other websites, take care of that adequately. A warning though - you need a thorough understanding of maths and signal processing before you can begin to "grok" wavelet transforms.

The sample this code comes from can be found on GitHub.

Implementation

The wavelet transform algorithm I used includes implementations of the Haar wavelet, and the biorthogonal 5/3 wavelet. In both cases, the algorithm is capable of transforming images of arbitrary sizes, not just powers of 2. In addition, the biorthogonal 5/3 wavelet implementation uses the lifting scheme (which is a second-generation wavelet transform). The lifting scheme speeds up the wavelet transform by factorising the transform into convolution operators, known as lifting steps, which reduce the number of arithmetic operations by nearly a factor of two.

The following code example shows a high level overview of the wavelet image upscaling process:

public static unsafe SKPixmap WaveletUpscale(this SKImage image, Wavelet wavelet)
{
    int width = image.Width;
    int height = image.Height;
    int upscaledWidth = width * 2;
    int upscaledHeight = height * 2;
                        
    float[,] y = new float[upscaledWidth, upscaledWidth];
    float[,] cb = new float[upscaledWidth, upscaledWidth];
    float[,] cr = new float[upscaledWidth, upscaledWidth];
    float[,] a = new float[upscaledWidth, upscaledWidth];

    image.ToYCbCrAArrays(y, cb, cr, a);

    WaveletTransform2D wavelet2D;
    WaveletTransform2D upscaledWavelet2D;

    switch (wavelet)
    {
        case Wavelet.Haar:
            wavelet2D = new HaarWavelet2D(width, height);
            upscaledWavelet2D = new HaarWavelet2D(upscaledWidth, upscaledHeight);
            break;
        case Wavelet.Biorthogonal53:
        default:
            wavelet2D = new Biorthogonal53Wavelet2D(width, height);
            upscaledWavelet2D = new Biorthogonal53Wavelet2D(upscaledWidth, upscaledHeight);
            break;
    }

    wavelet2D.Transform2D(y);
    wavelet2D.Transform2D(cb);
    wavelet2D.Transform2D(cr);
    wavelet2D.Transform2D(a);

    upscaledWavelet2D.ReverseTransform2D(y);
    upscaledWavelet2D.ReverseTransform2D(cb);
    upscaledWavelet2D.ReverseTransform2D(cr);
    upscaledWavelet2D.ReverseTransform2D(a);

    for (int row = 0; row < upscaledHeight; row++)
    {
        for (int col = 0; col < upscaledWidth; col++)
        {
            y[col, row] *= 4.0f;
            cb[col, row] *= 4.0f;
            cr[col, row] *= 4.0f;
            a[col, row] *= 4.0f;
        }
    }

    SKImageInfo info = new SKImageInfo(upscaledWidth, upscaledHeight, SKColorType.Rgba8888);
    SKImage output = SKImage.Create(info);

    SKPixmap pixmap = output.ToRGBAPixmap(y, cb, cr, a);
    return pixmap;                        
}

The WaveletUpscale method is an extension method that works on an SKImage object, and requires a Wavelet argument that specifies which wavelet to use. This method doubles the width and height of the source image, although this could be parameterised if required. The image data is first converted from the RGBA colour space to the YCbCrA colour space, and its the data from this colour space that's wavelet transformed using the supplied wavelet.

After the wavelet transform has been performed, the image data is in the frequency domain. If this data were simply reverse transformed, it would yield the original YCbCrA image data. However, here the frequency domain data is inverse transformed into an image of twice the size. A consequence of doing this is that the resulting YCbCrA data needs multiplying by a constant to return it to its correct form. This data is then converted back from the YCbCrA colour space to the RGBA colour space, for display.

This simple technique yields compelling results. I haven't included any images because images taken using modern devices already have a high resolution, which is then doubled by this process. It takes a lot of zooming into the image to see the detail achieved by the upscaling process. There's no blockiness introduced, instead any artefacts are of the type commonly found in wavelet-based compression schemes.

A quick examination of the research literature reveals that this technique can be improved upon by performing different interpolation techniques to the data in the frequency domain, rather than simply inverse transforming the data into an image of twice the size.

I've not included any Wavelet Transform code in this blog post, as it's quite extensive. However, the main things to note are that: (1) it uses the Parallel.For loop to take advantage of the multi-cores that are present in most mobile devices, (2) it uses the lifting scheme to reduce the number of arithmetic operations performed, (3) the code could most likely be further optimised so that multiple passes of the data aren't required, and (4) the code can be found on GitHub.

Wrapping up

While consumer imaging apps don't typically contain operations that wavelet transform image data, it's a mainstay of scientific imaging. As I've commented before, it's safe to say that Xamarin.Forms and SkiaSharp make a good combination for scientific imaging apps, particularly for the tablet form factor.

The sample this code comes from can be found on GitHub.

Friday, 14 August 2020

Connecting to localhost over HTTPS for (Xamarin) Android release builds

I've previously written several posts about connecting to localhost HTTPs services from Xamarin apps running on simulators and emulators. Over time this has become easier, and standardised, and my last post outlined the approach to take.

A user recently contacted me to point out that while my solution worked just fine in DEBUG mode, it didn't work in RELEASE mode on Android. This prompted much head scratching by me, and a journey into the heart of darkness, before I realised that I'm an idiot.

So the purpose of this blog post is to outline the solution to the issue, in the hope that others don't go down the rabbit hole I went down. It should also serve as a reminder to myself when I encounter this problem again in future.

Problem

There's a sample app that demos how to connect to localhost HTTPs services from iOS simulators and Android emulators. The idea is to ignore SSL errors on iOS/Android for local HTTPS services by setting the HttpClientHandler.ServerCertificateCustomValidationCallback property to a callback that ignores the result of the certificate security check for the localhost certificate. For more information about this, see Connecting to localhost over HTTPS from simulators and emulators (revisited).

The problem was that while the solution outlined in my previous blog post worked fine in both DEBUG and RELEASE mode in iOS, it only worked in DEBUG mode on Android. When switched to a RELEASE build, the sample app wouldn't retrieve any data from the web service, but provided no indication why not. It was frustrating! Note that both DEBUG and RELEASE modes on Android were set to use the AndroidClientHandler native network stack.

Journey

I convinced myself that the linker was linking out a type/namespace that was required. So I tried the usual trick of ensuring the type was referenced from C# in the Android project. That didn't help. Then I created a custom linker configuration (linker.xml) that specified key types and namespace to preserve. That also didn't help. I even tried turning the linker off. But still the app wouldn't retrieve any data from the localhost HTTPS web service.

I then had a diversion into wondering if it was related to the API level, so tried the app on different API levels. Still nothing.

Googling the issue didn't turn up anything similar. That was bizarre. No one had experienced anything remotely similar??? That made me wonder if it was a new issue, caused by the latest release of Mono. So I started looking at issues in the Mono.Android repo and while I found a few issues that initially looked promising, they were all dead ends.

So then I started to look at the code for the AndroidClientHandler class (in the Mono.Android repo), in the hope of finding an obvious property that needed setting, or method that needed calling. That didn't help either. I was just about to close my browser window when I noticed that AndroidClientHandler catches a number of Java exceptions. That got me thinking that maybe there's a native Java exception bubbling up, that AndroidClientHandler doesn't catch. So I added code to catch all Java exceptions to my Android project. That immediately revealed that a Java.Net.SocketException was occurring in Xamarin.Android.Net.AndroidClientHandler.DoProcessRequest (and wasn't being caught by AndroidClientHandler). The exception said that the socket failed due to an EACCES (permission denied) error.

It couldn't really be that simple could it??? It was.

Solution

Ensure the Android manifest has the internet permission enabled. That should have been the first check I made. Yes, it really was just that.

Note to future Dave: check the Android manifest first next time!

Thursday, 30 April 2020

Connecting to localhost over HTTPS from simulators and emulators (revisited)

Last year, I wrote about how to connect to localhost over HTTPS from iOS simulators and Android emulators. Connecting to localhost web services that run over HTTP is straight forward, however it’s more work when the web service runs over HTTPS. It typically involves:

  • Creating a self-signed developer certificate on your machine.
  • Choosing the network stack to use.
  • Specifying the address of your local machine.
  • Bypassing the local development certificate check.

Connect to local web services from iOS simulators and Android emulators documents how to carry out the above steps. However, I recently discovered that the approach for bypassing the local development certificate check on iOS no longer worked.

At the time of writing the original blog post, the CoreFX implementation of HttpClient was in most Mono profiles, but not in iOS. The recommendation for iOS was to use the managed network stack, and the ServicePointManager API. However. since then iOS support for the CoreFX HttpClient implementation has dropped, which hooks into the NSUrlSession network stack. This ultimately means that now, on both iOS and Android, the approach for bypassing the local development certificate check is identical.

SSL errors can be ignored on iOS and Android for local HTTPS web services by setting the HttpClientHandler.ServerCertificateCustomValidationCallback property to a callback that ignores the result of the certificate security check for the localhost certificate:

public HttpClientHandler GetInsecureHandler()
{
    HttpClientHandler handler = new HttpClientHandler();
    handler.ServerCertificateCustomValidationCallback = (message, cert, chain, errors) =>
    {
        if (cert.Issuer.Equals("CN=localhost"))
            return true;
        return errors == System.Net.Security.SslPolicyErrors.None;
    };
    return handler;
}
This method must be in a class in a platform project, even if the HttpClient object is constructed in a shared project. The HttpClientHandler object returned by the GetInsecureHandler method should then be passed as an argument to the HttpClient constructor:

HttpClient client = new HttpClient(GetInsecureHandler());
The advantage of using this approach is that it hooks into the native network stacks on both iOS and Android (NSUrlSession on iOS, AndroidClientHandler on Android).

For a full sample that demonstrates this approach in Xamarin.Forms, see my GitHub repo.

Wednesday, 8 April 2020

Combining Xamarin.Essentials WebAuthenticator with IdentityModel.OidcClient

Previously I wrote about using the Xamarin.Essentials WebAuthenticator class to initiate an authentication flow with IdentityServer, and wait for a callback/redirect to the redirect URI scheme. Although my blog post didn’t show it, there was an IdentityService class that created the URI for the authorization endpoint, including the code challenge, CSRF token, and other required query parameters. This class also handled exchanging the received authorization code for an access token, which could then be used in API calls.

While this approach works well, it does require a detailed knowledge of the OAuth 2.0 spec for native apps. While that’s certainly something I’d recommend understanding fully, it would also be convenient if there was a library that did the heavy lifting for you.

Enter IdentityModel.OidcClient

IdentityModel.OidcClient is a C#/.NET standard 2.0 OpenID certified library for native mobile and desktop applications. It’s a small, simple, and elegant library that I strongly recommend for communicating with IdentityServer.

The library has two modes:

  1. In manual mode, OidcClient helps you with creating the necessary start URL and state parameters, but you need to coordinate with the browser you want to use. Then, when the browser work is completed, OidcClient can take over to process the response, get the access/refresh tokens, and contact other endpoints.
  2. In automatic mode, you can encapsulate all browser interactions by implementing the IBrowser interface. Then, authentication and token requests become one line of code.

I’ve previously used manual mode for interacting with IdentityServer, when I had to write my own platform code for invoking native browsers. However, now that WebAuthenticator does that for you, it’s much simpler to use automatic mode and wrap WebAuthenticator using the IBrowser interface.

The IBrowser interface is used to model a browser, and specifies that a single method named InvokeAsync requires implementing, which returns a Task<BrowserResult>. The InvokeAsync method should just invoke your browser to initiate the authentication flow, and return the response in a BrowserResult object.

A Browser class that implements the IBrowser interface, which uses WebAuthenticator to provide the browser interactions, is shown below:

public class Browser : IBrowser
{
    public async Task InvokeAsync(BrowserOptions options, CancellationToken cancellationToken = default)
    {
        WebAuthenticatorResult authResult =
                await WebAuthenticator.AuthenticateAsync(new Uri(options.StartUrl), new Uri(Constants.RedirectUri));
        return new BrowserResult()
        {
            Response = ParseAuthenticatorResult(authResult)
        };
    }

    string ParseAuthenticatorResult(WebAuthenticatorResult result)
    {
        string code = result?.Properties["code"];
        string scope = result?.Properties["scope"];
        string state = result?.Properties["state"];
        string sessionState = result?.Properties["session_state"];
        return $"{Constants.RedirectUri}#code={code}&scope={scope}&state={state}&session_state={sessionState}";
    }
}

Here, the InvokeAsync method calls the WebAuthenticator.AuthenticateAsync method, and returns the parsed response in a BrowserResult object.

An authentication flow can then be initiated by a OidcClient object as follows:

OidcClient _oidcClient;
LoginResult _loginResult;

var options = new OidcClientOptions
{
    Authority = Constants.AuthorityUri,
    ClientId = Constants.ClientId,
    Scope = Constants.Scope,
    RedirectUri = Constants.RedirectUri,
    ResponseMode = OidcClientOptions.AuthorizeResponseMode.Redirect,
    Browser = new Browser()
};

_oidcClient = new OidcClient(options);
_loginResult = await _oidcClient.LoginAsync(new LoginRequest());

Here, an OidcClientOptions object is created that defines the precise authentication flow that will be invoked with IdentityServer. The OidcClientOptions object also defines the IBrowser object that's used to provide browser interactions. An OidcClient object is then created, and its LoginAsync method is invoked to initiate the authentication flow. The result of the authentication flow will then be stored in a LoginResult object, which can include different tokens (access, identity, refresh) along with other data.

The access token can then be used in subsequent API calls to IdentityServer:

HttpClient _httpClient;

_httpClient = new HttpClient();
_httpClient.BaseAddress = new Uri(Constants.ApiUri);
_httpClient.DefaultRequestHeaders.Authorization
    = new AuthenticationHeaderValue("Bearer", _loginResult?.AccessToken ?? string.Empty);

HttpResponseMessage response = await _httpClient.GetAsync("test");
string content = await response.Content.ReadAsStringAsync();

if (response.IsSuccessStatusCode)
{
    editor.Text = JArray.Parse(content).ToString();
}
else
{
    editor.Text = response.ReasonPhrase;
}

Here, the access token is retrieved from the LoginResult object and set as the bearer token in the Authorization header of a HttpClient object: The HttpClient object then makes a call to a specific IdentityServer API, using the access token for authorization.

Wrapping up

This blog post has shown how to combine the Xamarin.Essentials WebAuthenticator class with the IdentityModel.OidcClient library to initiate authentication flows, and process the response. The advantage of combining WebAuthenticator with OidcClient is that (1) you don’t have to write any platform code to invoke native browsers, and (2) you don’t need to write any code to generate the correct authentication query parameters. In addition, because OidcClient is OpenID certified, you have peace of mind that it’s correctly implemented the OAuth 2.0 spec for native apps. For more information about the IdentityModel.OidcClient library, see Building mobile/native clients in the IdentityModel docs.

The sample this code comes from can be found on GitHub.

Tuesday, 7 April 2020

Authentication from a Xamarin.Forms app using WebAuthenticator

The OAuth 2.0 for Native Apps spec represents the best practices for OAuth 2.0 authentication flows from mobile apps. These include:
  • Authentication requests should only be made through external user agents, such as the browser. This results in better security, and enables use of the user’s current authentication state, making single sign-on possible. Conversely, this means that authentication requests should never be made through a WebView. WebView controls are unsafe for third parties, as they leave the authorization grant and user’s credentials vulnerable to recording or malicious use. In addition, WebView controls don’t share authentication state, meaning single sign-on isn’t possible.
  • Native apps must request user authorization by creating a URI with the appropriate grant types. The app then redirects the user to this request URI. A redirect URI that the native app can receive and parse must also be supplied.
  • Native apps must use the Proof Key for Code Exchange (PKCE) protocol, to defend against apps on the same device potentially intercepting the authorization code.
  • Native apps should use the authorization code grant flow with PKCE. Conversely, native apps shouldn’t use the implicit grant flow.
  • Cross-Site Request Forgery (CSRF) attacks should be migitated by using the state parameter to link requests and responses.
Ultimately, the OAuth 2.0 authentication flow for native apps becomes:
  1. The native app opens a browser tab with the authorisation request.
  2. The authorisation endpoint receives the authorisation request, authenticates the user, and obtains authorisation.
  3. The authorisation server issues an authoration code to the redirect URI.
  4. The native app receives the authorisation code from the redirect URI.
  5. The native app presents the authorisation code at the token endpoint.
  6. The token endpoint validates the authorization code and issues the requested tokens.
I’d previously written a Xamarin.Forms sample to do this without any additional libraries, and one with the IdentityModel.OidcClient2 library, which is OpenID certified. Both samples consume endpoints on a publically available IdentityServer site, and use platform code to invoke native browsers.

Enter Xamarin.Essentials


Xamarin.Essentials recently introduced a WebAuthenticator class, which is a web navigation API that can be used for authentication with web services. The class contains a single method, AuthenticateAsync, which starts an authentication flow by navigating to a specified URI, and then waits for a callback/redirect to the redirect URI scheme. The whole point of the class is that it avoids having to write platform code to invoke native browsers. For more information, see the WebAuthenticator doc.

I decided to test drive WebAuthenticator by using it to initate an authentication flow with IdentityServer. However, an authentication flow from a mobile app should include a web backend between the mobile app and the authentication provider. This is to avoid including client secrets in your mobile app, which is considered to be insecure. However, I’ve avoided a web backend as middleware here, as I just want to see what’s involved in authenticating with IdentityServer using the WebAuthenticator class.

However, before you can use WebAuthenticator to authenticate with IdentityServer, you first have to perform some platform specific setup.

Platform setup


On Android, you require an IntentFilter to handle your redirect URI. This can be accomplished by subclassing the Xamarin.Essentials WebAuthenticatorCallbackActivity class:
[Activity(NoHistory = true, LaunchMode = LaunchMode.SingleTop)]
[IntentFilter(new[] { Intent.ActionView},
    Categories = new[] { Intent.CategoryDefault, Intent.CategoryBrowsable},
    DataScheme = "io.identitymodel.native",
    DataHost = "callback")]
public class WebAuthenticationCallbackActivity : WebAuthenticatorCallbackActivity
{
}
You’ll also need to overide the OnResume method in your MainActivity class, to call back into Xamarin.Essentials:
protected override void OnResume()
{
    base.OnResume();
    Xamarin.Essentials.Platform.OnResume();
}
On iOS, you’ll need to add your app’s redirect URI to your Info.plist file:
 <key>CFBundleURLTypes</key>
 <array>
  <dict>
   <key>CFBundleURLName</key>
   <string>WebAuthenticatorDemo</string>
   <key>CFBundleTypeRole</key>
   <string>None</string>
   <key>CFBundleURLSchemes</key>
   <array>
    <string>io.identitymodel.native</string>
   </array>
  </dict>
 </array>
You’ll also need to overide the OpenUrl method in your AppDelegate class, to call back into Xamarin.Essentials:
public override bool OpenUrl(UIApplication app, NSUrl url, NSDictionary options)
{
    if (Xamarin.Essentials.Platform.OpenUrl(app, url, options))
        return true;

    return base.OpenUrl(app, url, options);
}
On UWP, you need to declare your redirect URI in your Package.appxmanifest file:
    <Applications>
      <Application Id="App"
        Executable="$targetnametoken$.exe"
        EntryPoint="WebAuthenticatorDemo.UWP.App">
        ...

        <Extensions>
          <uap:Extension Category="windows.protocol">
            <uap:Protocol Name="io.identitymodel.native">
              <uap:DisplayName>WebAuthenticatorDemo</uap:DisplayName>
            </uap:Protocol>
          </uap:Extension>
        </Extensions>
      
      </Application>
    </Applications>

Start an authentication flow with WebAuthenticator


An authentication flow with IdentityServer can be started by creating the start URI, and passing it to the AuthenticateAsync method along with the redirect URI:
string url = identityService.CreateAuthorizationRequest();
var authResult = await WebAuthenticator.AuthenticateAsync(new Uri(url), new Uri(Constants.RedirectUri));
If you aren’t familiar with the process of building an authorization request to IdentityServer, I recommend you look at the IdentityService class in the sample. It involves creating a long query string that contains data that indicates to IdentityServer what kind of authentication flow to kick off.

The AuthenticateAsync method returns a WebAuthenticatorResult object, which contains a number of properties, such as AccessToken, and RefreshToken. More importantly, it has a Properties dictionary that contains all of the query string or URI fragment properties, that can then be accessed by their key. Therefore, the Properties dictionary can be parsed to build the raw response sent by IdentityServer:
string raw = ParseAuthenticatorResult(authResult);
authorizeResponse = new AuthorizeResponse(raw);

string ParseAuthenticatorResult(WebAuthenticatorResult result)
{
    string code = result?.Properties["code"];
    string idToken = result?.IdToken;
    string scope = result?.Properties["scope"];
    string state = result?.Properties["state"];
    string sessionState = result?.Properties["session_state"];
    return $"{Constants.RedirectUri}#code={code}&id_token={idToken}&scope={scope}&state={state}&session_state={sessionState}";
}
The result of a successful authentication flow is that the AuthorizeResponse object contains an authorization code, that can then be exchanged for the requested tokens:
UserToken userToken = await identityService.GetTokenAsync(authorizeResponse.Code);
If the UserToken object contains an access token, it can be used when making API calls to IdentityServer:
var content = await identityService.GetAsync($"{Constants.ApiUri}test", userToken.AccessToken);

Wrapping up


Although there's a lot going on here, in terms of generating the start URI, and processing the response from the redirect URI, using the WebAuthenticator class has avoided having to write platform code to invoke native browsers. This can be a considerable amount of work, depending on the platform, and it can also be confusing if you aren't familiar with the intricacies of each platform.

I've deliberately left out a lot of code from this blog post, which generates the queries to send to IdentityServer. This is because my aim wasn't to cover the intricacies of IdentityServer, but instead how to initiate an authentication flow with WebAuthenticator, and how to process its response. I also said that all of this is simple, but it’s only simple if you have a good understanding of the OAuth 2.0 spec. If you don’t, it can be overwhelming. Therefore, in my next blog post I’ll look at simplifying all of this by using the IdentityModel.OidcClient library.

The sample this code comes from can be found on GitHub.