Xamarin Forms (Android) - Why won't it store my Azure B2C Auth Token?

December 5, 2016

I’ve been playing with the Microsoft Azure Active Directory B2C sign-in and sign-up management on mobile devices recently. It’s actually quite good, and does a lot of heavy lifting. Anyone who’s ever had to roll their own back-end authentication & authorisation services will appreciate the time and effort that this saves.

However it can be a little quirky. One of the gotchas I came across was that it wasn’t persisting the MSAL auth token on Xamarin Android when I built a release version of the app. Every time I restarted the app, it would ask me to sign in again. Rebuilding and/or running it in debug mode would ‘fix’ it, but the moment I switched back to a release version, it would stop working again. I realised it had something to do with the token not being read or persisted properly; though why it would work in debug mode but not release mode was beyond me.

But I can’t be the only person experiencing this problem, right?

First stop (as usual): Google, followed by Stack Overflow. Oh look, here’s someone with the same problem. Sadly he hasn’t been able to fix it either…

My initial thought it might be related to some arcane (or brand new) Android permissions thing. Tho access to the Android Shared Preferences store isn’t anything special, and it certainly shouldn’t be dependent on your build config…

I started to take a look at their UserTokenCache implementation to figure out why it wasn’t working, and then fix it. But it was late, and I was tired, so I found a simpler solution in one of the example repos in GtHub for building a native desktop app.

In that example, they point the MSAL PublicClientApplication at a rudimentary FileCache object for storing the auth tokens:

ClientApplication = new PublicClientApplication(your.b2c.AuthContext, your.b2c.ClientId)
{
  RedirectUri = "urn:ietf:wg:oauth:2.0:oob",
  UserTokenCache = new FileCache(),
};

by default most examples leave the UserTokenCache property empty, which forces the engine to use whatever they’ve defined as the platform default, which of course is where this is falling down

So I had a look at their FileCache object in that same repo. Yep. Simple enough.

I plugged that into my code, and added a bunch of logging to the bits where it reads and writes the auth token, to see what it was doing. My theory was that it was more likely to have a problem writing the token than when reading it.

private void AfterAccessNotification(TokenCacheNotificationArgs args)
{
  // if the access operation resulted in a cache update
  
  try
  {
    this.Log().Debug("About to update token cache (if it's changed)...");
    if (this.HasStateChanged)
    {
      this.Log().Debug("State has changed, updating cache file...");
      lock (FileLock)
      {
        _file.WriteAllBytes(CacheFilePath, this.Serialize());
        
        // once the write operation took place, restore the HasStateChanged bit to false
        
        this.HasStateChanged = false;
      }
      this.Log().Debug("Token cache file updated");
    }
    this.Log().Debug("Finished updating token cache file");
  }
  catch (Exception ex)
  {
    this.Log().ErrorException($"Something went wrong in AfterAccessNotification: {ex.Message}", ex);
  }
}

True enough, when running the app in debug, the storage and retrieval of auth tokens worked just fine. But when I rebuilt in release mode and ran it (I broke the exception message up over several lines for legibility):

12-05 09:22:03.434	LGE Nexus 5X	Error	11208	ZB.Droid	  at System.Runtime.Serialization.Json.JsonFormatWriterInterpreter.WriteValue 
(System.Type memberType, System.Object memberValue) [0x00268] in <40b76ff8e1454ef49c9939320c700e1b>:0

12-05 09:22:03.434	LGE Nexus 5X	Error	11208	ZB.Droid	
                    FileCache: Something went wrong during token 
                    AfterAccessNotification: No set method for property 'OffsetMinutes' in type 
                    'System.Runtime.Serialization.DateTimeOffsetAdapter'.: 
                    System.Runtime.Serialization.InvalidDataContractException: 
                    No set method for property 'OffsetMinutes' in type 
                    'System.Runtime.Serialization.DateTimeOffsetAdapter'.

12-05 09:22:03.362	LGE Nexus 5X	Info	11208		4/12/2016 10:22:03 PM:  - TokenCache.cs: Serializing token cache with 1 items.

OK. Well that answers that then. An exception related to there not being a setter for a DateTimeOffset-related property somewhere deep in the bowels of the MSAL library. Ugh!

However, once I wrapped the contents of the AfterAccessNotification in a try-catch with that handler to log that the exception had occurred, it all started working perfectly.

This doesn’t fix the problem though. I mean sure, you can stick with using this FileCache implementation, but it does mean that the auth tokens aren’t encrypted or protected like they would be if they were in the platform’s SharedPreferences store. Keep this in mind if you go this route to solve any other problems: you should implement a more secure storage mechanism than this one.

The Fix

Turns out it’s a pretty simple fix, after this. Henrik (from the original Stack Overflow question) indicated that simply telling the compiler to NOT link in the System.Runtime.Serializaton library during a release build fixed the problem at his end.

This is easy to do: simply open the project properties for the Android project, make sure your build output is set to your Release config, and then in the “Android Options / Linker” tab, add System.Runtime.Serialization to the text box.

Don't link System.Runtime.Serialization

This will ensure that when the app is compiled, the serialsation code/setter for that DateTimeOffset property won’t be excluded by being ‘linked out’.

Alternatively, you can add an entry to your Android project file, under the Release property group condition, which does the same thing:

<AndroidUseSharedRuntime>False</AndroidUseSharedRuntime>
<AndroidLinkSkip>System.Runtime.Serialization;</AndroidLinkSkip>
<AndroidLinkMode>SdkOnly</AndroidLinkMode>

Remember to CLEAN and then rebuild, and then it should Just Work. Thanks Henrik for the update! :)

If you now remove the FileCache class and all references to it, it’ll all go back to working the way it should have.

Also: There is an issue on the iOS Simulator (there’s a shock!), that requires you to fiddle with the Entitlements.plist file in order to access the secure Shared Storage. If this is an issue for you on the simulator, and you’re only using the Shared Storage by default to store this token, then you can use this FileCache method to get around it.

Some (personal) comments on the Azure B2C Service

Azure AD B2C has its quirks, and imo they have some work to do. Especially around enabling us to do ‘headless’ signups and logins. And I really wish they’d finish it up and bring their nuget library out of -alpha. Come on guys, you promoted the hell out of this thing at //Build, but it’s like you’ve barely touched it since?

Pros:

  • It’ll get you up and running quickly. There’s a ton of blog posts out there which tell you how to get it done and up and running. And I have to say, it is relatively painless.

  • It’s (mostly) platform agnostic, so there’s ways to get it done using .NET, JavaScript, UWP, Cordova (may the gods have mercy on your soul) and so on.

  • It’s Azure, and it’s ‘GA’ (‘Generally Available’) in more than the USA now.

  • It’s based on Microsoft’s Active Directory, which has been around for a very long time. It’s probably not going away any time soon.

  • If you want to be able to provide email sign-up services to your users, instead of having to proxy their auth via one of the other social network identity providers, this does a reasonable job.
    • Speaking of using other social network IDPs (e.g. Google, Facebook, Twitter), if you’ve felt the refresh-token-sync pain, then you should definitely give it a go.
  • As of fairly recently, you can now completely customise and style the web interface for the sign-up/sign-in WebView that pops up on mobile devices.

  • It has flows for user initiated password reset, and allowing users to manage their details themselves. If you’ve ever had to implement this kind of thing yourself, you’ll know what a time saver this is!

Cons:

  • Auth tokens can’t be written away properly to the Android SharedPreferences secure store when the app is built in ‘release’ mode without footling with the linker settings. Heh :)

  • Actually, the biggest gripe I have with it is that I can’t (at the time of writing) do a ‘headless’ login. I’m forced to hook up their ‘use a platspec custom renderer so we can embed a webview in your content page’ process. It’s S.L.O.W af, and I hate using ‘loading…’ spinners to tell my users “no we haven’t crashed or got stuck, but you can stare at this blank screen while we’re waiting for something to happen”.

  • If you have a shitty mobile internet connection (hello Australia!), it can take upwards of a minute to load the aforementioned sign-in/sign-up pages. Substantially less than optimal. I, and many others, would like to be able to use the standard mobile interface controls and drive an OAuth2/OpenIdConnect flow behind the scenes.

They keep alluding that you can do this. But I’ve not been able to find a way that doesn’t require popping up a web browser instance to load a web page to pull in some some javascript to pull in some form elements and an internal token which is then submitted as part of the request to get an access token… I mean… Rube Goldberg much?!

If anyone out there has managed to crack the ‘headless login’ issue without using a web browser of some sort, then please let us know in the comments!


comments powered by Disqus