Most of the time you can't think about what's hidden around the corner. It doesn't matter how much time you spend thinking about doing something, you won't be able to properly measure the effort until the job is done.
The assignment looks simple on the surface: moving all the pages of a hybrid mobile application to be locally stored on the mobile device to create a better experience for the user.
The mobile application was working fine before. But it's using online pages that (duh!) take time to load. If only we could store all those pages as part of the mobile package, the browsing experience would be faster for the user, since only dynamic content would have to be loaded (and we are using AJAX calls for that already.)
To finally paint the full picture, the application authenticates using OAuth2 with the backend services. The token information is stored in a server (that we started calling "proxy" and haven't stopped since) and a surrogate session ID is created for the client to use (thus avoiding sharing the token and refresh_token with the client app.)
By the way, to make matters a little bit more complicated, since the mobile application is just a native wrapper to a responsive web page, we are reusing all assets and code in our regular web application: only one web application that works on your desktop browser and is also embedded in a wrapper running on your mobile device.
Here is my best attempt to draw the entire architecture of the application (sorry for the messy illustration):
From online to offline
Back to what we wanted to do: making our hybrid mobile application load pages stored offline in the mobile device instead of going out there to the server to load them.
I thought this was going to be easy. We had it like that before anyways (before OAuth2 was introduced in the picture, and early enough that authentication was hardcoded in the app.) Probably we just needed to flip the switch again and move it all back offline.
We quickly realized that things were going to be more interesting this time around. Look at this workflow:
- User loads the mobile app
- A locally-stored (offline) page loads up asking them to sign in.
- User it's redirected to the OAuth2 (online) authentication screen (this happens through the proxy server.)
- After specifying username and password, user is taken to the OAuth2 (online) authorization screen.
- After allowing access to our application, the OAuth2 server redirects the user back to our proxy server, including all the necessary authentication information.
- Our proxy server then creates a surrogate session (to avoid exposing the token with an unsecured client), saves it in a cookie for the client to read, and redirects the user to the main page of the site.
So far, all it's good for the web, but remember we want that "main" page of the site to also be locally stored in the mobile device. How can we get our proxy server to redirect to a page that isn't online?
A while ago I learned how to intercept web requests in the embedded browser of the mobile device and cancel or change them to whatever I needed.
So that's what we started doing: whenever we were ready to display our main (online) page, it was as simple as stopping the request from the browser, and replacing it with the locally stored offline version of the page.
When using the regular web version, the user would get the online main page after authenticating, but when running from inside our mobile wrapper, the user would get the locally stored version of the same page, because our code replaced it on the fly.
Unfortunately, I failed to see where this model gets a little more complicated.
Where the heck is my cookie?
Offline pages and cookies do not get along.
A cookie needs a domain. An offline page stored in a mobile device doesn't have one. It's a page that we load using the file:// protocol thus can't access cookies saved by an actual website.
Our proxy server creates a session that's served and saved as a cookie on the client side. As soon as we made our pages work offline, there was no session saved, so users couldn't log in anymore.
I thought this was going to be easy. Now it turned to be 10 times more complicated than what I anticipated.
Right away we discussed about a cookieless session. You know, those where you send the session information as part of the URL of the page.
This was very common back in the day when
users companies loved to disable the access to cookies in the browser. There are still several websites that use this approach.
The advantage of this mechanism was that we could fix our mobile issue with a well-known and easy to implement approach, but security-wise I had a concern: what happens if a user shares a URL to our application that includes the session information? We would have to protect from this scenario by checking IP addresses or I-don't-know-what-else, but this was definitively something to think about.
There had to be a better way.
Cookieless for mobile. Cookies for the web.
We decided to use both approaches: cookies for the regular web application, and an embedded session string as part of the URL for the mobile application. This way we avoided the security issue with the cookieless session (since the mobile application doesn't display the URL) and kept the web application working as it was.
Depending on the application, we started asking the OAuth2 server to redirect the user to a different URL on the proxy server. This way, we implemented a handler for creating and managing cookieless sessions and another one for the regular cookie mechanism.
A couple of nights (and days) later, the final solution was working perfectly fine.
What I got from the experience
First and foremost, this was another example of why estimation is so hard in a complex field like Software Engineering. Sometimes, what seems super simple can blow up to unimagined proportions.
The second lesson is how hard work pays off at the end. We could have easily settled with the online (slow) version of the application, but because we pushed hard we accomplished what we really wanted.
Finally, we all ended with an enormous satisfaction of implementing something clever that makes using our application a little bit better.
Nothing like this feeling to fuel your passion to return next day to work.