Authentication, state and single-page apps in ClojureScript

I’ve been spending a lot of time recently working on a single-page app in ClojureScript, most recently using the recently-released, and impressive, re-frame project, which builds an FRPish unidirectional data flow on top of the ClojureScript react interface reagent. By a single page app, I mean one with almost no server-side code, which could theoretically be served from a static HTML page, and which only lives at a single URL (modifying the #fragment part of the URL to navigate between logical “pages” in the app, as is the vogue these days).

I’ve tried out a few different approaches to writing this app, including with Om and plain reagent, and re-frame seems to have just the right combination of simplicity and abstraction for my taste. With reagent I sometimes get the feeling I’ve had in backbone (JavaScript) projects, that there’s not really very much structure in the framework and I have to make a lot of stuff up on my own, which is fine but raises the question of why I’m using a framework at all. Om seems pretty neat, and definitely has a structure to it, but I’m not crazy about the ways it relies on async channels to communicate things between components. (In fairness, I’ve probably spent the least amount of time with Om, partly because the tutorials for it seem a little esoteric.)

At any rate, the app as it currently exists is pretty simple. There is a home page, from which the user is prompted to log into an OAuth service (in my case, foursquare). When the user hits the link, he or she will be redirected to foursquare to authorize my app, after which he or she will arrive back at my site with an authorization token in the URL (this is the “callback URL”, cf foursquare). I need the token in order to pass it along to foursquare for API requests I subsequently make to get the user’s check-in history and so forth.

With a full-stack application this would be pretty easy – my server-side code could look for the callback URL, and when it’s found it could grab the token, redirect the user to a known “thanks for logging in” page, and pass the token along to the user’s browser in a cookie, or embedded on the page, or though any number of other well-known server/browser mechanisms.

With a purely client-side app, however, the situation is trickier. In general, the user is expected to stay on a single web page the entire time he or she is using the app, without refreshing; application state sort of accumulates over time in the DOM and JavaScript object models on the page. But since the user needs to redirect to foursquare’s site in order to authenticate, my application now effectively has two entry-points: one when the user first navigates there, and one when the user returns from authentication. Indeed, since in the long term I want my app to authenticate against at least one more OAuth service, and probably several (spotify, twitter, etc), it will have an arbitrary number of entry points.

This complicates managing user state, since the user’s previous state (as reflected in the page’s object model) will be completely destroyed when he or she leaves the site for authentication. Upon the user’s return, the sum total of his or her state will essentially consist of the callback URL, including the authentication token. This is somewhat manageable with a single OAuth provider; when the user hits the callback URL, our client-side code can stash the token in the page’s object model somewhere and then use something like history.replaceState() to modify the URL back to the landing page. With more than one OAuth provider this approach is problematic, since the state will disappear when the user hits the second provider.

So we need a way to persist information between page refreshes. The two most obvious methods are cookies and HTML5 localStorage, and I will be using localStorage (or sessionStorage) because it is new and shiny. With that said, there are still two viable approaches I can see to this.

  1. User chooses to authenticate to foursquare, is redirected to foursquare, and then arrives back at the site with an empty app state and an auth token in the URL. The client-side code stashes the auth state in localStorage and uses replaceState() to navigate back to the home page.
  2. When the user hits the “log in” button, we open a popup. In the popup, the user is redirected to foursquare. When the user comes back to the popup page after authentication, the popup sets the token value in localStorage and closes itself. When localStorage is set, this will trigger a "storage" event in the parent window, which can then react by updating its state (to say “thanks for logging in” or the like).

There are a lot of things I like about the second approach. Because the original page persists while the popup is open, it can just trundle along as it had before, waiting for the storage event to fire; this simplifies state management in the parent window. However, it has a two considerable drawbacks:

Firstly, desktop browsers tend to block popups invoked from javascript these days, and honestly, thank god they do. There is probably a way to work around this, maybe by using an <a target="_blank"> tag or the like, but most of my experiments so far have triggered the popup blocker in my browser.

Secondly, popups kind of suck in mobile browsers. They work, more or less, but they don’t feel native to the mobile experience.

In passing, I’ll note that using an iframe might also be a technical solution to the two above problems, but I don’t really want to because (a) the user should see the address bar in an OAuth situation to validate that they’re not on a phishing site, and (b) ick, iframes.

So it seems like a straightforward redirect is the way to go. In my next installment, I’ll dig a little deeper into what this means for state management in the app, how that works with re-frame, and into client-side routing in ClojureScript generally.

Advertisements
Leave a comment

4 Comments

  1. Tim

     /  April 21, 2015

    I’m looking forward to the second part of this series – I’m particularly interested in seeing how you handle generating URLs, and updating the hash without re-rendering the page.

    Reply
  2. I’m also curious to see what you’ve found and how re-frame’s handler setup fits in.

    Reply
  3. It only took a year and a half, but I’ve written a follow-up!

    Reply
  1. Adventures in client-side routing with re‑frame and OAuth | Tim Gilbert's Blog

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 )

Twitter picture

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

Facebook photo

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

Google+ photo

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

Connecting to %s

%d bloggers like this: