Facebook Login from a Standalone PWA
I’m a computer nerd. Formerly a professional one. I still use my programming skills in my magic. I wrote my show control software, and do several routines that involve custom programming. Occasionally I encounter technical problems problems where my Google skills don’t turn up a clear solution. If I do find a solution, I’ll try to document it for future Googlers to find. This is one of those posts.
So, if you’re trying to integrate Facebook login into a PWA running in standalone mode, read on! Everyone else will probably want to turn away now.
Facebook login works great from websites. But try to run a progressive web app in standalone mode, and suddenly, it’s not so simple. The login popup goes blank, and nothing happens. My searching for a solution turned up a few breadcrumbs, but no complete working solution. I did get it working, though.
Apparently there are conversations about changing the way standalone PWA’s work to simplify OATH. But for now, here’s what worked for me.
In short, I set up a custom login flow with Facebook. It created a login popup that opened Facebook’s OATH link which, in turn, forward to a special page I set up. That page in the popup window communicated with the standalone window through Window.postMessage() to pass the token back, so the standalone window could make API calls. The standalone then closed the popup.
Here is the longer version:
My app used Window.open() to create a popup window calling Facebook’s OATH URL. You may have different needs, but my call looks like this (link here has been anonymized and breaks added for readability):
https://www.facebook.com/v3.2/dialog/oauth
?client_id=9999999999
&display=popup
&redirect_uri=https://example.com/login.htm
&state=aaaaaaaaaa
&auth_type=rerequest
&response_type=token
&scope=manage_pages,publish_pages
- client_id is my app id.
- display=popup tells FB to format the login to fit nicely in a popup window.
- redirect_url is my page to accept the login data. Important note: This page must be set up as a “Valid OAuth Redirect URI” under the Facebook Login Settings of your Facebook app.
- state is just a code to prevent cross site request forgery attacks. I generate a random code and verify the same code comes back in the response from Facebook.
- type=rerequest tells FB to reauthorize the permissions, if they aren’t already granted.
- response_type=token tells FB to respond with a token.
- scope is the permissions that should be requested with this login attempt.
See Facebook’s documentation for setting up a custom login flow for more details.
After Facebook completes the login process, it forwards to your “redirect_url” in your popup window. The relevant data, including the token will be in the URL’s fragment (the part following the #). That page will need to pass that information back to your main window. Below is the script I used on that page. It just converts the fragment to JSON and passes it back for processing.
// Remove hash and spit into array
// of "XXX=YYY" values
let fragmentParts =
window.location.hash.slice(1).split("&");
let myJson = {};
// Convert hash elements to JSON
// E.g. XXX=YYY becomes myJSON.XXX="YYY"
fragmentParts.forEach(function (part) {
let parts = part.split("=");
myJson[parts[0]] =
decodeURIComponent(parts[1]);
});
// Send JSON data to main window
window.opener.postMessage(myJson,
"https://example.com");
The standalone window has a listener set up. When it gets the message from the login window, it closes the popup.
The standalone page also has a timer that regularly checks if the login page was closed before it sent back a message.
The main window now has the token it needs to make Facebook API calls. The problem is getting the Facebook SDK to use it. Normally, the SDK retrieves the token, stores it in a cookie, and manages it for you. If you don’t use the standard login method from the SDK, this won’t happen.
You’ll need to store the token somewhere and pass the token manually on every API call. For example:
FB.api("/me/accounts",
{
access_token: "access token goes here",
other_fields_as_appropriate: "??????????"
},
function (response) {
//process the return from the api call
});
As I didn’t use the FB.login, I couldn’t use FB.logout. So, to log out I just deleted the copy of the token, effectively ending the session.
I don’t know if this is the best solution, but it worked for me. Hopefully it will help someone else.