Progressive Web Apps with Oauth: Don’t Repeat My Mistake

This week, I learned a simple technique that anyone building Progressive Web Apps that use Oauth (or anyone building web apps with authentication) will find useful. It will keep your login experience smooth and seamless for your mobile users.

Before we get into it:

“What are Progressive Web Apps?” — A progressive web app is a web app that is built to use many of the advantages of native mobile applications, such as home-screen launch, connectivity independence, and push notifications.

Here’s an in depth article explaining them, and here’s a quick tutorial from Scott Domes on how to build one. If you’re not familiar with Oauth, it’s the protocol that allows our apps to use other services for authentication (facebook, google etc.), and is necessary for using their APIs. I have brief introduction on it in my previous article.

Our Little Project

A few weeks ago, I was on a team in charge of building oxcord, a collaborative jukebox that uses Spotify’s API, and we decided to make that app progressive.

Everything worked fine, until I saved the app to my home screen and launched it from there.

Our Little Problem

In the full-screen app-mode, when I clicked ‘login with Spotify’, and instead of navigating to the login page, like it did in the browser, it opened that page in an in-app browser window.

Since our app uses Oauth, we had registered a redirect uri (ocxocrd.io/***), which the user was sent back to after being authenticated. The problem was that I was sent back to the domain of our app, while still in this in-app browser.

This meant that I was now forced to use our app in this awkward in-app browser window, as opposed to the full-screen app-mode of the PWA.

In addition, the full-screen instance of the app was also still running in the background.

What we needed to figure out was this: How can we let our user log in without actually leaving the app.

How I made it Work

2 Simple Words: Pop Ups

3 Easy Steps:

  1. Open a new window from the main screen.
  2. Log in with opened window
  3. Close the child window an send auth data back to opener (main) window

This solution was suggested by a mentor, and after doing some digging, I found that it’s a popular method. As a matter of fact, the code I ended up finding and using to make this work had already been written by staff at Spotify to smooth out their login process.

Details

HML5 has a little known API called the window.postMessage, which allows windows and frames to send data across domains to one another.

Let’s look a simplified example to see exactly how this would work.

I put together a small demo that opens a pop up for logging in. Upon login confirmation, the pop up closes and the info is displayed on the main page.

Before looking at the code, it would help to see a visual representation of how it works.

  1. The popup is open and a variable is created to reference it.
  2. The user logs in through a form in the pop up window
  3. The form submission triggers postMessage, which sends the form data to the main window
  4. An event listener in the main window detects the event and triggers a callback
  5. The callback updates the data in the DOM
  6. The callback closes the popup by referencing the variable it was stored in

Here’s what it looks like in code

A) Main Window

<!--index.html-->

<script>
// 1. open popup window
function login(){
popup = window.open ("popup.html","mywindow", "width=350,height=250");
}
// 2. listen for message from popup
window.addEventListener('message', updateAuthInfo);
// 3. update html when event is detected
function updateAuthInfo(e){
document.querySelector("#username").innerText = e.data.username;
document.querySelector("#password").innerText = e.data.password;

// 4. close popup
popup.close();
}
</script>
...

B) Popup Window

<!--popup.html-->
<script>
var loginForm = document.querySelector('#myForm');
...
// 1. listen for submission of login form
loginForm.addEventListener('submit', event => {
event.preventDefault();
  // 2. store form data as object
const authData = storeFormData();
  // 3. send object in message to main (opener) window
window.opener.postMessage(authData, "http://localhost:3000");
});
</script>

I made a repo with the full code, so you can use to try it yourself.

Note: for postMessage to work, there must me a hostname, which means the files have to be served. In my repo, I set up a simple express server. Also, some browsers name the events and event listeners differently, so you’ll have to account for that. There is a more robust example in this gist that handles compatibility.

And that’s it!

For our app, we used the same concept as our example. The login button opens a popup that leads to Spotify’s login page, and the redirect uri takes the user to an empty dummy page from the server, whose only purpose is to postMessage the authentication data to the main window and then close itself. I set this up using the same server code that José M. Pérez created for his Spotify Player app.

And just like that, we had a seamless login experience, and a full-screen progressive web app in all it’s glory.

I hope you found this useful. Thanks for reading!