Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Performance of Stateless Authentication in SPA and SSO (Single Sign On)

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 common use case? - I allow the user to login via Google/GitHub or something, then get data from some stateless REST API

like image 692
Jiew Meng Avatar asked Aug 11 '13 03:08

Jiew Meng


People also ask

What is stateless authentication?

Stateless Authentication is a way to verify users by having much of the session information such as user properties stored on the client side. It makes identify verification with the backend seamless. Also called token-based authentication, stateless authentication is efficient, scalable, and interoperable.

What is the difference between stateful and stateless authentication?

Stateful Authentication is a way to verify users by having the server or backend store much of the session information, such as user properties. It is simpler to implement than Stateless or Token-Based Authentication but it is resource-intensive causing the server to perform lookups for every request.

Is OAuth stateless or stateful?

AM OAuth 2.0-related services are stateless unless otherwise indicated; they do not hold any token information local to the AM instances. Instead, they either store the OAuth 2.0/OpenID Connect tokens in the CTS token store, or present them to the client.

Is REST API stateless or stateful?

Q. Is REST API stateless or stateful? A. REST APIs are stateless because, rather than relying on the server remembering previous requests, REST applications require each request to contain all of the information necessary for the server to understand it.


2 Answers

Disclaimer :)

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:

  1. 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 entity board to the correct user.

  2. The said token has nothing to do with remote services' tokens. The logic they are computed 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.

  3. 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
  4. 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 has id: MyApp_def456. You create also an authToken with your own logic that will represent the user MyApp_def456 and pass it to the client (cookies are ok!!)

  5. 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 comparable 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 compute 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 compute 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.

like image 184
namero999 Avatar answered Sep 28 '22 09:09

namero999


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).

like image 20
danielepolencic Avatar answered Sep 28 '22 10:09

danielepolencic