If I have a SPA (Single Page Application - developed with BackboneJS) and want to have a stateless RESTful backend API for its data. I like how 3rd party single sign on makes things so easy for the user, thus will like it use it.
But I understand in a stateless environment like this, authentication is done on every request? If so, if I am using a 3rd party SSO, eg. GitHub, won't I need to goto GitHub to authenticate the user everytime? Whats the best practice for such situations? I believe its a very mon use case? - I allow the user to login via Google/GitHub or something, then get data from some stateless REST API
If I have a SPA (Single Page Application - developed with BackboneJS) and want to have a stateless RESTful backend API for its data. I like how 3rd party single sign on makes things so easy for the user, thus will like it use it.
But I understand in a stateless environment like this, authentication is done on every request? If so, if I am using a 3rd party SSO, eg. GitHub, won't I need to goto GitHub to authenticate the user everytime? Whats the best practice for such situations? I believe its a very mon use case? - I allow the user to login via Google/GitHub or something, then get data from some stateless REST API
Share Improve this question asked Aug 11, 2013 at 3:29 Jiew MengJiew Meng 88.3k192 gold badges523 silver badges832 bronze badges 1- if you authenticate the page, you should be golden until a refresh, which in an SPA is never... – dandavis Commented Aug 21, 2013 at 21:51
3 Answers
Reset to default 14 +125Disclaimer :)
Having implemented such a thing for my product, and sharing many of your concerns and tech (especially SPA with Backbone using a 100% stateless REST backend) I can tell you what are my thoughs about this, making clear that this doesn't want to be "the answer" but rather a conversation starter to learn from the resulting discussion, as I think I too have quite a bit to learn on the topic.
First of all, I think you should go 100% stateless. And by 100%, I mean 100% :) Not only your API layer should be stateless, but the whole application (except client, of course). Moving sessions on a different layer (eg. redis) just moves the problem a bit, but doesn't solve it. Everything (especially scaling) will be so much easier, and you will thank yourself about this decision later on.
So, yes, you need to have authentication on every request. But this doesn't mean that you have to hit the auth provider every time. One of the things that I learned is that allowing an user to authenticate via FB/GitHub/Whatever (from now on, remote service) is just a mean to ease the pain of signup/signin, nothing else. You still have to grow your personal database of users. Of course, each user will be associated to a "remote" users, but soon after the authentication has been performed, the app should reference "your" user, and not the "remote" user (eg. GitHub user).
Implementation
Here's what I've implemented:
My API methods always need an authentication token. The auth token is a hash that represent an user of my system, so that when I call
POST /api/board?name=[a_name]&auth=[my_token]
, I know who's calling, can check permissions and can associate the newly created entityboard
to the correct user.The said token has nothing to do with remote services' tokens. The logic they are puted from is specific to my app. But it maps an user of mine, that is also mapped to a remote user, so no information is lost in case it's needed.
Here's how I authenticate the user via a remote service. I implement the remote authentication as specified in the service documentation. Usually it is OAuth or OAuth-like, this means that in the end I get an
authToken
that represent the remote user. This token has 2 purposes:- I can use it to call API methods on the remote service acting as the user
- I have the guarantee that the user is who it says it is, at least by the means of the remote service
As soon as your user authenticated itself with the remote service, you load or create the matching user in your system. If the user with
remote_id: GitHub_abc123
is not present in your system, you create it, otherwise you load it. Let's say this user hasid: MyApp_def456
. You create also anauthToken
with your own logic that will represent the userMyApp_def456
and pass it to the client (cookies are ok!!)Back to point 1 :)
Notes
The authentication is performed at every request and this means that you deal with hashes and crypto functions, that by definitions are slow. Now, if you use bcrypt
with 20 iterations, this will kill your app. I use it to store the passwords of the users when they log in, but then use a less heavy algorithm for the authToken
(I personally use an hash parable to SHA-256
). This tokens can be short lived (let's say less than the average time to crack them) and are fairly easy to pute on a server machine. There's not an exact answer here. Try different approaches, measure and decide. What I am sure about, instead, is that I prefer to have this kind of problems than session problems. If I need to pute more hashes, or faster, I add CPU power. With sessions and a clustered environment, you have memory issues, load balancing and sticky sessions problems, or other moving pieces (redis).
Obiouvsly, HTTPS
is absolutely mandatory because authToken
is always passed as a parameter.
The way I would implement it is by introducing a proxy between the client (Backbone) and the RESTful webserver. The proxy manages authentication of users in conjunction with SSO. Therefore no need to change the api and/or client/webserver. Here a quick demo:
var http = require('http'),
httpProxy = require('http-proxy'),
express = require('express');
var proxy = new httpProxy.RoutingProxy();
var app = express();
function ensureAuthenticated(req, res, next) {
if (isLoggedIn) { return next(); }
res.redirect('/');
}
// This should be your (RESTful) webserver
http.createServer(function (req, res) {
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.write('request successfully proxied!' + '\n' + JSON.stringify(req.headers, true, 2));
res.end();
}).listen(9000);
var isLoggedIn = false;
app.get('/', function(req, res){
console.log(isLoggedIn)
res.send('Logged in? ' + isLoggedIn);
});
app.get('/login', function(req, res){
isLoggedIn = true;
res.redirect('/');
});
app.get('/logout', function(req, res){
isLoggedIn = false;
res.redirect('/');
});
app.all('/api/*', ensureAuthenticated, function(req, res) {
return proxy.proxyRequest(req, res, {
host: 'localhost',
port: 9000
});
});
app.listen(8000);
The first time you visit the page, you're logged out and any call to /api/something
gets redirected to /
. When you're logged (visit the /login
page) all requests to /api/*
are routed through the proxy to the webserver listening on port 9000.
In particular, when you set app.all('/*', ...)
all the calls to your API server stay the same but are augmented with an authentication layer. The concept is trivial to extend with oauth (have a look at passportjs if you are using node).
You could adopt the approach used in Facebook's JavaScript SDK.
This documentation page provides the quick-start to login vith Facebook for web. Not very deep, but explains the basic of how to use their approach. Does not mention the possibility of signature, however.
When registering Facebook logon for your app, you get an app secret from the application dashboard on Facebook.
When the user have logged on to your app via Facebook, your JavaScript gets an authentication object. This object contains a signature. (If you set it up correct in the dashboard.)
You can supply this authentication object with the client call to your RESTful server, and on the server check that the signature is correct. This way you know that the user was authenticated by facebook, is this user, and was authenticated for your application.
This documentation page describes how to use the signed authentication. Do not be intimidated by the "games" in the headline, it works perfectly fine for any web app.
Instead of allowing only Facebook for SSO, you can implement something in the same spirit as the FB login, using other OAUTH providers.
Use the solution suggested by danielepolencic, but modify it such that instead of a proxy in the same node.js instance you have another server for logins. This service does the OAUTH check with the provider, and maintains session with the client. It emits a signed token to the client, with a short time-to-live. The client must ask for a new token before the time-to-live ends.
You then implement a client-side JavaScript with functionality similar to Facebook JavaScript SDK for the login to be used by your application. This function can either poll for new tokens, or retrieve a new token on request, whatever is most efficient for the scenario.
The client supplies this token to the RESTful API on each request, and the server checks the signature. Just as for the Facebook SSO.
There still is a session, but it can be maintained visavis pletely different machines. This service can be scaled independently of the servers with your RESTful api.
However, bear in mind that this approach can be susceptible to man-in-the-middle attacks and replay attacks. Probably unwise to use without https.