Digging into failed redirects within Android Webviews

 

Header Photo by The Nigmatic on Unsplash

Discovering the issue

It was the 14th December and we were winding down ready for the Christmas break. Our code freeze had kicked in several days previously and we had shipped an update at that time to get a couple of priority bug fixes before the holidays. We are currently working on some bigger updates on our beta branch, so there hasn’t been any bigger updates merged into master recently — so all should be good for us to settle down, right?

At the start of the week we had about two reports of failed Instagram account connections previously — we had taken a look into these but the issue was completely un-reproducible from our side. What was happening was users were attempting to carry out the authentication flow for Instagram profiles, this means a WebView is opened and the user authenticates their profile, which then triggers communication with our servers to complete an OAuth flow — giving us access to the users Instagram account and adding it to their connected accounts with Buffer. Instead of this flow completing though, the WebView would just hang on the authentication screen without redirecting and completing the OAuth flow.

Now, the authentication code in our project hadn’t been touched in at least 2 months, so there were no recent changes that could cause any issues here. Come Friday though, these reports started to jump up and suddenly the issue was reproducible by more than several people at Buffer. Something was definitely up here — I’d like to take some time to share the findings here and what was done to fix it.


Troubleshooting the issue

What was weird about this issue was that it was re-producable by some members of the team, but myself and another team member were able to complete the OAuth flow without any issue. And with these inconsistency, there was no consistent pattern such as location — so this made it a little trickier to nail down. We initially tried a few things to double check that there wasn’t some form of device / account configuration that was causing an issue — it’s always a good idea to check these first as these kind of issues can be resolved without needing to ship an update:

  • Clearing the browser cache
  • Removing the Instagram account from any other Buffer accounts
  • Revoking access to Buffer through the Instagram settings panel, forcing reconnection
  • Forcing the use of a different WebView implementation on our device
  • Attempt the same flow within another app

None of the above changed anything except for the last one — Attempt the same flow within another app. We managed to find another app that made use of the Instagram OAuth flow, and things within that app were working fine. This helped us to narrow it down to something with the implementation of the WebView itself in our app.


Solving the issue

At this point I started digging into the documentation of the WebView class to spot anything that could potentially cause some issues. The WebView class has a lot of different settings that you can configure — in most cases you don’t really need to touch these on Android,. Whilst you could enabled everything, this would come with security and performance implications which in turn would make it difficult to debug should an issue arise from some configuration, so it’s important to only enable what you know you need for your application.

The WebView class takes these settings in the form of a WebSettings instance — if you take a look at that link (if you haven’t seen the WebSettings class before) then you may notice that there are a lot of things that you can set. Whilst searching through this documentation there was one thing that stood out to me that we didn’t make use of, this was the setDomStorageAsEnabled(). Now, this setting is disabled by default and is responsible for flagging if the WebView has the ability to make use of the DOM storage API.

Why did this stand out to me though? Well, the WebStorage API is made up of two mechanisms — we have session storage which provides temporary storage for the lifetime of the browser and local storage which is lives beyond the lifetime of the browser. Each of these storage mechanisms makes use of a separate storage object and the data from each of these can be set, retried and deleted when the WebStorage is enabled within the Android WebView. Now, if a website required the use of this to operate and the WebView implementation within your app does not have this enabled then things will not operate as expected.

What is odd is that whilst the OAuth flow within our WebView was broken for some of our team, a couple of user were still able to authenticate correctly. One team member was even able to complete authentication when we initially had the two reports from users, but failed to authenticate a few days later. Because of these differences, I came to the assumption that something had changed recently within the OAuth flow for Instagram and there was data stored within the browser cache for us that had persisted since then, meaning that we could still successfully authenticate. But for the members of our team who could not longer authenticate, the cache was not present — meaning it may have expired since it was last stored. And now because these possible changes, the WebStorage API was not enabled meaning that authentication was now broken without the ability to store the required data.

Whilst this is a lot of assumptions to make, enabling the WebStorage API fixed the issue for our team members and in turn our users also. Because of this, we are fairly confident that the above is the cause (or at least close to the cause) of why this issue surfaced.


Conclusion

I hope the sharing of this issue provides some context into how this part of the WebView API works, and maybe helps you out with this in future. Remember though, it’s not a great idea to enable everything within the WebView by default. Whilst these issues may pop-up, it will be easier for you, and more secure for your application, to track down the solution without everything else enabled when it isn’t needed — as this could introduce many other variables into the implementation as well as the debugging process.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s