I work at a small consultancy and we often make web apps for our clients. One part of the web app that is often repetitive to write is the authentication system. In a lot of our web apps we would like to support OAuth login from the various providers as well as email-based registration.
However, programming this in every web application is time-consuming. Ideally what we'd like is a generic, authentication proxy server that sits in front of the application server and handles everything related to authentication (including login, registration, and logout). I'm wondering if something like this would be secure? Or if there is any security-related reason why it should not be done like this?
I'm imagining it would look like this:
Auth Server
- This is basically a reverse proxy server. It has its own database with information related to authentication (for example, email address, password, etc).
- It checks each request for a specific cookie with session information (for example, a JWT in a cookie called
SESSION
). This session information would be proof that the user has logged in correctly at some point in the past. If the session information is correct, it adds anX-UserId
header to the request and forwards it to the Application Server. If there is no cookie with authentication information, or if the session information is incorrect, then it forwards the request to the application server without theX-UserId
header. - It will have specific routes for logging-in, logging-out, and registration (for example,
/login
,/logout
, and/register
). It will handle these requests completely on it's own. The Application Server will not even be aware of these requests.
Application Server
- This is a normal web application server.
- It performs no authentication, relying entirely on the
X-UserId
header from the Auth Server. - It is not accessible from the outside world. The only way to access it is by going through the Auth Server.
- It also has its own database. It is filled with all the information related to the application (except for authentication information).
Here is an example of how login could work, as well as the interaction between the Auth Server and Application Server:
Here are the steps for getting the login.html
page.
- The client sends a request for
/login.html
. - The Auth Server receives the request. The Auth Server tries to look for a cookie called
SESSION
. It doesn't exist, so it forwards the request to the Application Server without adding aX-UserId
header. - The Application Server receives the request. There is a route defined for
/login.html
and it doesn't require theX-UserId
header to exist, so it returns the HTML forlogin.html
. - The Authentication Sever receives the response, and forwards it back to the user.
The login.html
page could contain the JS code for actually logging in. Here is one possible way to do it.
- The client sends an AJAX request to
/login/email
to login using an email address and password. - The Auth Server receives the request. Since this is a request for the Auth Server, it does not forward the request to the Application Server.
- The Auth Server looks at the request body for an email address and password. It checks the email address and password in its Auth DB.
- If the email address and password are correct, it returns an
HttpOnly
cookie calledSESSION
that contains a JWT.
After this succeeds, the user could be directed to /home.html
. The steps for this are similar to /login.html
, except that the user is actually required to be authenticated:
- The user sends a request to
/home.html
. - The Auth Server receives the request. The Auth Server checks the
SESSION
cookie and decodes the JWT. The Auth Server gets the User Id from the database based on the information in the JWT. Let's say that the User Id is 5. - The Auth Server adds a
X-UserId=5
header to the request and forwards it to the Application Server. (In the case where theSESSION
cookie doesn't exist, or the JWT is not able to be successfully decoded, the Auth Server will forward the request to the Application Server without adding aX-UserId
header. The following steps are written assuming that the authentication succeeds and the Auth Server adds the `X-UserId header.) - The Application Server receives the request. Since the request is for
/home.html
, the Application Server knows that a valid User Id is required, so it checks theX-UserId
header. - The Application Server successfully gets the User Id from the
X-UserId
header and uses it to pull data out of the Application DB and build thehome.html
page. - The Application Server sends the response with the
home.html
page to the Auth Server. - The Auth Server receives the response, and forwards it back to the User.
Here is some additional information that didn't really fit above:
- The Auth Server would remove the
X-UserId
header if sent by a user before forwarding the request to the Application Server. - The Auth Server could be relatively general, and used on multiple different projects. The Application Server wouldn't have to worry at all about authentication.
- The Application Server would need to be in charge of authorization (for example, making sure that each user could only edit their own information).
- The Auth Server would completely handle new user registration. This could include an OAuth flow (for using OAuth as provided by Google, Twitter, Facebook, etc), as well as simple email-based registration. When a new user registers, the Auth Server would create a User Id and link it to the user's email address, Google username, Twitter username, etc. The Application Server would only ever see this User Id (never the user's email address, Google username, Twitter username, etc).
Is a setup like this secure?
X-UserId
by sending it directly by the user (looks like you handled this), by somehow bypassing the Auth Server or by bugs like header injection this looks like a sound setup. Just to be sure the Auth Server might maybe replay-safe signX-UserId
so that it definitely cannot be spoofed.X-UserId: Id Time HMAC
whereHMAC=hash(shared_secret, Time, Id)
. If having a shared secret is not an option you could also use public key cryptography (i.e. RSA or similar) and do a digital signature with the private key and validate it with the public key. Also, such values might not only leak by somebody sniffing but by your application reflecting input data to the user, for example in error messages. Insofar it would be better if a value can not be replayed (therefore timestamp which should be verified by the application to be current).