Jwenky: An Express API Server with User Authentication

Written by balden | Published 2020/08/22
Tech Story Tags: nodejs | express | jwt | mysql | react | authentication | api | cyber-security | web-monetization

TLDR Jwenky is an API server coded in Express framework. The server can be one standalone server, functioning as both an Authentication and an Access server. The Jwt is signed by the Auth server with the private RSA key. The client submits both the Jwt, and the cookie, with each request it sends to the Access servers. The system uses a refresh token, in a cookie, to refresh the JWT, which is valid for 20 hours. The whole system does not need any additional XSRf protection; it is Xsrf proof by design. Both the server and the client are available in the Github repo.via the TL;DR App

Jwenky is an API server coded in Express framework.
Why one more API server in Express?
The reason I did this project is some security considerations about Jwt. How to implement a really secure authentication system, without the use of an external identity provider?
A simple Jwt implementation with default configuration, will use a symmetric HS256 signing algorithm. OWASP [JSON Web Token for Java], and IETF [RFC8725], mention that using symmetric keys for Jwt signing, is vulnerable to brute-force attack.
Practically, the attacker has immediate access to the secret key hash. It is included in the Jwt signature. They can just use a brute-force cracker, to acquire the original server secret key. The brute-force attack has very high probability to be successful, because it happens offline.

Single guard

I will not analyze here all the security implications of using a simple HS256 algorithm as the single security measure for a system. If someone likes to search more about this subject, there are quite a lot references in the two documents, that I mentioned above from OWASP, and IETF.
The suggested implementation for real security is using an RSA algorithm, together with fingerprinted tokens. This is the implementation applied in this project.
The server can be one standalone server, functioning as both an Authentication and an Access server. The best use case scenario is to have separated microservices running. Typically one Auth server, and many Access servers as needed.

Dual channel, Dual security

Two guards
In this project the Jwt is signed by the Auth server with the private RSA key. In the Github repo, I've included the RS256-keys.md file, with instructions how to generate a pair of RSA keys, and use them in the project. The code can read the keys form the .env file, or directly for the key files.
In addition, the Jwt includes the hash of a random value (fingerprint). The random value itself is sent in a cookie.
Both the server and the client are available in Github as Jwenky-srv and Jwenky-cnt.
After the user successful login, the client gets the Jwt in a header, and the fingerprint in an http only cookie, by the Auth server. The Jwt-token is stored in client's sessionStorage. The client submits both the Jwt, and the cookie, with each request it sends to the Access servers.
The Access server verifies the Jwt, using its public RSA key, and then compares the fingerprint from the cookie, to its hash in the Jwt.
Even if the token is stolen by an Xss attack, the attacker can not modify it, because they can not add the fingerprint on it.
On the other hand, an Xsrf attack will fail, because the Xsrf request can not submit a Jwt in a header, although it automatically submits the fingerprint in the cookie. The whole system does not need any additional Xsrf protection; it is Xsrf proof by design.

Refresh cookie

The access token will be short lived, depending on the required security level. Usually 3-5 mins for highly secure systems. Up to 30 mins for systems, which do not need very high protection. In this project the default Jwt validity is for 10 minutes, configurable in "/config/auth.js".
To refresh the Jwt, the system uses a refresh token, in a cookie. The refresh token is not a Jwt. The refresh cookie contents are a UUID, and a random value. This cookie is valid for 20 hours, configurable in the same file as above. It is configured with a "path" parameter, so it is not submitted in each request. It is only sent to the Auth server, not to the Access server.
The same UUID, with the user ID, and the hash of the random value are stored in the database. A user may have multiple active refresh tokens, if he/she is logged-in in different devices simultaneously.
When the client requests a refresh, the system verifies the validity of the refresh token against the stored hash, and provides a new Jwt access token for the specific session.
By clicking the "Logout" button, only the current refresh token is deleted. After logout, a new button appears "Logout - All Sessions", which gives the ability to the user to delete all active refresh tokens.

Client

The code reads the token data by just decoding it. The client does not verify the token. We do not need to protect html and css markup. It's the data sent by the Access server that need to be protected, at the server side.
The protected pages in this demo client do not retrieve any real content from the server. They include a request to retrieve user data, simulating a regular client behavior.
There are two user levels. Regular user and administrator. The "/dashboard" page requires administrator level privileges. If a non admin user tries to access it, they will be sent back to the previous page.

Server - client clock difference.

The client is tolerant in clock differences between server and client. By default it will sent a refresh request, if an access request is to be sent 30 seconds before Jwt expiration.
Configurable in "/components/config/auth.js". This feature is explained in more detail in client's Readme.md file.

Written by balden | Freelance developer
Published by HackerNoon on 2020/08/22