Stateless authentication in SPA and SSO (Single Sign On)

If I have a SPA (single-page application developed using BackboneJS), and you want to have backend APIs for your data without being tied to a RESTful object. I like the way a third-party one-time sign makes things so easy for the user, so he will like it.

But I understand that in such an environment without authentication, each request is executed? If so, if I use third-party SSO, for example. GitHub, I don’t need GitHub to be able to authenticate the user every time? What is the best practice for such situations? I find this a very common use case? - I allow the user to log in via Google / GitHub or something like that, and then receive data from any API without saving the status

+6
source share
3 answers

Denial of responsibility:)

Having implemented such a thing for my product and sharing many of your problems and technologies (especially SPA with Backbone, using a 100% backend without taking into account the state of inactivity), I can tell you what my opinion is, it’s clear that this is not so, I want to be the "answer", but rather the conversation starter, to learn from the final discussion, since I think I also need to participate a little in this topic.


First of all, I think you should go 100% stateless. And 100%, I mean 100% :). Not only your API level should be inactive, but the entire application (with the exception of the client, of course). Moving sessions to another level (e.g. redis) will move the problem a bit, but it does not solve the problem. Everything (especially scaling) will be much simpler, and you will later thank yourself for this decision.

So yes, authentication is required for each request . But this does not mean that you have to hit the provider every time. One of the things that I learned is that allowing the user to authenticate through FB / GitHub / Whatever (henceforth, the remote service) is just a means to ease the pain of registering / signing, nothing more. You still have to create your personal user database. Of course, each user will be associated with users of "remote", but soon after authentication, the application should refer to "his" user, and not to the user "remote" (for example, the GitHub user).

Implementation

Here is what I implemented:

  • My API methods always need authentication tokens. The authentication token is the hash that represents the user of my system, so when I call POST /api/board?name=[a_name]&auth=[my_token] , I know who is calling, can check permissions and can associate the newly created board object with the correct user.

  • The specified token has nothing to do with remote service tokens. The logic they compute is specific to my application. But it displays my user, which is also displayed on the remote user, so no information is lost if necessary.

  • Here is how I authenticate the user through a remote service. I implement the remote authentication specified in the service documentation. Usually this is OAuth or OAuth-like, which means that in the end I get authToken that represents the remote user. This token has 2 goals:

    • I can use it to call API methods in a remote service acting as a user
    • I have a guarantee that the user is the one who says that this is at least using a remote service.
  • Once your user authenticates with a remote service, you download or create the corresponding user on your system. If the user with remote_id: GitHub_abc123 not on your system, you create it, otherwise you will download it. Let's say this user has id: MyApp_def456 . You also create an authToken with your own logic that will represent the user MyApp_def456 and pass it to the client (cookies are ok !!)

  • Back to point 1 :)


Notes

Authentication is performed for each request, and this means that you are dealing with hashes and cryptographic functions, which are by definition slow. Now, if you use bcrypt with 20 iterations, this will kill your application. I use it to store user passwords at login, but then use the less heavy algorithm for authToken (I personally use a hash comparable to SHA-256 ). These tokens can be short-lived (even if less than the average time to crack them) and it is quite easy to calculate on a server machine. There is no exact answer. Try different approaches, measure and decide. Instead, I’m sure that I prefer to have these kinds of problems besides the problems with the sessions. If I need to calculate more hashes or faster, I will add processor power. Sessions and a clustered environment have memory problems, load balancing, and problems with sticky sessions or other moving parts (redis).

Obiouvsly, HTTPS absolutely required, since authToken always passed as a parameter.

+9
source

The way I will implement this is to introduce a proxy server between the client (Backbone) and the RESTful web server. A proxy manages user authentication in conjunction with SSO. Therefore, there is no need to change the api and / or client / web server. Here is 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 a page, you are logged out and any call to /api/something redirected to / . When you register (visit the /login page), all requests to /api/* sent through the proxy server to the web server listening on port 9000.

In particular, when you install app.all('/*', ...) , all calls to your API server remain unchanged, but are complemented by an authentication level. The concept is trivial to extend with oauth (see passportjs if you use node).

+1
source

You can use the approach used in the JavaScript JavaScript SDK.

This documentation page provides a quick way to log in with Facebook for the web. Not very deep, but explains how to use their approach. However, the possibility of a signature is not mentioned.

When you register with Facebook for your application, you get the application secret from the application bar on Facebook.

When a user logs into your application via Facebook, your JavaScript will receive an authentication object. This object contains a signature. (If you configured it correctly in the control panel.)

You can provide this authentication object with a client call to the RESTful server, and check the signature on the server. Thus, you know that the user has been authenticated by facebook, is that user and has been authenticated for your application.

This documentation page describes how to use signed authentication. Do not be alarmed by the β€œgames” in the title; it works great for any web application.

Instead of only allowing Facebook for SSO, you can implement something in the same vein as the FB login using other OAUTH providers.

Use the solution proposed by danielepolencic, but change it so that instead of a proxy server in the same instance of node.js you have another server to log in to. This service performs OAUTH verification at the provider and maintains a session with the client. It issues a signed token to a client with a short lifetime. The client must request a new token before the expiration date.

You then implement client-side JavaScript with functionality similar to the JavaScript JavaScript SDK login function that your application will use. This function can either interrogate new tokens, or receive a new token upon request, which is most effective for the scenario.

The client delivers this token to the RESTful API for each request, and the server verifies the signature. Like for SSO on Facebook.

There is still a session, but you can support visavis with completely different machines. This service can scale independently of servers using a RESTful api.

However, remember that this approach may be susceptible to man-in-the-middle attacks and repeated attacks. It might not be wise to use without https.

0
source

Source: https://habr.com/ru/post/951476/


All Articles