Before start, you need to have the following tools installed on computer: Git, Node.js and/or Yarn. MySQl::Workbench.
- Quick Start
- API
- Sessions
- Cookies
- Client Storage
- Sessions vs. JWT
- Options for Auth in SPAs / APIs
- Why not JWT?
- How to encrypt and decrypt in nodejs
- 2. Simple Encryption and Decryption (two-way)
- errorcodes messages π¦
- Libraries used π
- License
The Stack:
React + Express + Axios .
- React Scaffolding
- React-router
- Postgres
- NodeJS Express web server
Security:
- Username and Password Sign-in and Sign-up
- HelmetJS for header protection mechanisms
- TLS/SSL By default
- XSS protections
- CSRF protections
- Secure sessions
Browsers support
Install backend and Frontend by running either of the following:
Install NodeJS LTS from NodeJs Official Page (NOTE: Product only works with LTS version)
Clone the repository with the following command:
https://github.com/amariwan/reactJs-Mysql-Auth.git
Run in terminal this command:
cd backend && npm i && cd ../Frontend/ && npm i
- First, generate a key file used for self-signed certificate generation with the command below. The command will create a private key as a file called key.pem.
openssl genrsa -out key.pem
- Next, generate a certificate service request (CSR) with the command below. Youβll need a CSR to provide all of the input necessary to create the actual certificate.
openssl req -new -key key.pem -out csr.pem
- Finally, generate your certificate by providing the private key created to sign it with the public key created in step two with an expiry date of 9,999 days. This command below will create a certificate called cert.pem.
openssl x509 -req -days 9999 -in csr.pem -signkey key.pem -out cert.pem
Create a file called .env in the root of your project, and then add all the environment variables you need into this using the following format:
cp -r env .env
info about .env variable
host = "" // Host name for database connection. //! localhost
user = "" // Database user. //! root
password = "" // Password for the above database user.
port = "" // Port number for database connection. //! 3306
database = "" // Database name. //! auth_db
SERVERPORT = "" // Server port //! 4000
SESSION_SECRET = "" // Session secret //! Harley Davidson
algorithm = "" // Algorithm used to generate the encryption //! aes-256-ctr
secretKey = "" // Secret key for the above encryption //! vOVH6sdmpNWjRRIqCc7rdxs01lwHzfr3
viByte = "" // Encryption key for the above encryption //! 16
OriginFrontendServer = "" //frontend server //! localhost:8080
Then run this command to start your local server
npm start
or
npm start
Server will listen on port yourServerPort
, and it expose the following APIs:
-
POST -
/auth/register
- Register a new user- name - string
- lastname - string
- username - string
- email - string
- password - string
-
POST -
/auth/login
- Login user- email - string or username - string
- password - string
- user submits login credentials, e.g. email & password
- server verifies the credentials against the DB
- server creates a temporary user session
- sever issues a cookie with a session ID
- user sends the cookie with each request
- server validates it against the session store & grants access
- when user logs out, server destroys the sess. & clears the cookie
- every user session is stored server-side (stateful)
- memory (e.g. file system)
- cache (e.g.
Redis
orMemcached
), or - DB (e.g.
Postgres
,MongoDB
,MySQL
)
- each user is identified by a session ID
- opaque ref.
- no 3rd party can extract data out
- only issuer (server) can map back to data
- stored in a cookie
- signed with a secret
- protected with flags
- opaque ref.
- SSR web apps, frameworks (
Spring
,Rails
), scripting langs (PHP
)
Cookie
header, just likeAuthorization
orContent-Type
- used in session management, personalization, tracking
- consists of name, value, and (optional) attributes / flags
- set with
Set-Cookie
by server, appended withCookie
by browser
HTTP/1.1 200 OK
Content-type: text/html
Set-Cookie: SESS_ID=9vKnWqiZvuvVsIV1zmzJQeYUgINqXYeS; Domain=example.com; Path=/
- signed (
HMAC
) with a secret to mitigate tampering - rarely encrypted (
AES
) to protected from being read- no security concern if read by 3rd party
- carries no meaningful data (random string)
- even if encrypted, still a 1-1 match
- encoded (
URL
) - not for security, but compat
Domain
andPath
(can only be used on a given site & route)Expiration
(can only be used until expiry)- when omitted, becomes a session cookie
- gets deleted when browser is closed
HttpOnly
(cannot be read with JS on the client-side)Secure
(can only sent over encryptedHTTPS
channel), andSameSite
(can only be sent from the same domain, i.e. no CORS sharing)
- unauthorized actions on behalf of the authenticated user
- mitigated with a CSRF token (e.g. sent in a separate
X-CSRF-TOKEN
cookie)
- user submits login credentials, e.g. email & password
- server verifies the credentials against the DB
- sever generates a temporary token and embeds user data into it
- server responds back with the token (in body or header)
- user stores the token in client storage
- user sends the token along with each request
- server verifies the token & grants access
- when user logs out, token is cleared from client storage
- tokens are not stored server-side, only on the client (stateless)
- signed with a secret against tampering
- verified and can be trusted by the server
- tokens can be opaque or self-contained
- carries all required user data in its payload
- reduces database lookups, but exposes data to XSS
- typically sent in
Authorization
header - when a token is about to expire, it can be refreshed
- client is issued both access & refresh tokens
- used in SPA web apps, web APIs, mobile apps
- open standard for authorization & info exchange
- compact, self-contained, URL-safe tokens
- signed with symmetric (secret) or asymmetric (public/private) key
HTTP/1.1 200 OK
Content-type: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI1YmQ2MWFhMWJiNDNmNzI0M2EyOTMxNmQiLCJuYW1lIjoiSm9obiBTbWl0aCIsImlhdCI6MTU0MTI3NjA2MH0.WDKey8WGO6LENkHWJRy8S0QOCbdGwFFoH5XCAR49g4k
- contains header (meta), payload (claims), and signature delimited by
.
atob('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9')
// "{"alg":"HS256","typ":"JWT"}"
// β algorithm β type
atob('eyJzdWIiOiI1YmQ2MWFhMWJiNDNmNzI0M2EyOTMxNmQiLCJuYW1lIjoiSm9obiBTbWl0aCIsImlhdCI6MTU0MTI3NjA2MH0')
// "{"sub":"5bd61aa1bb43f7243a29316d","name":"John Smith","iat":1541276060}"
// β subject (e.g. user ID) β claim(s) β issued at (in seconds)
- signed (
HMAC
) with a secret- guarantees that token was not tampered
- any manipulation (e.g. exp. time) invalidates token
- rarely encrypted (
JWE
)- (web) clients need to read token payload
- can't store the secret in client storage securely
- encoded (
Base64Url
) - not for security, but transport- payload can be decoded and read
- no sensitive/private info should be stored
- access tokens should be short-lived
- client-side script injections
- malicious code can access client storage to
- steal user data from the token
- initiate AJAX requests on behalf of user
- mitigated by sanitizing & escaping user input
-
JWT can be stored in client storage,
localStorage
orsessionStorage
localStorage
has no expiration timesessionStorage
gets cleared when page is closed
Browser key-value store with a simple JS API
- domain-specific, each site has its own, other sites can't read/write
- max size higher than cookie (
5 MB
/ domain vs.4 KB
/ cookie)
- plaintext, hence not secure by design
- limited to string data, hence need to serialize
- can't be used by web workers
- stored permanently, unless removed explicitly
- accessible to any JS code running on the page (incl. XSS)
- scripts can steal tokens or impersonate users
- public, non-sensitive, string data
- private sensitive data
- non-string data
- offline capabilities
- session IDs are opaque and carry no meaningful data
- cookies can be secured with flags (same origin, HTTP-only, HTTPS, etc.)
- HTTP-only cookies can't be compromised with XSS exploits
- battle-tested 20+ years in many langs & frameworks
- server must store each user session in memory
- session auth must be secured against CSRF
- horizontal scaling is more challenging
- risk of single point of failure
- need sticky sessions with load balancing
- server does not need to keep track of user sessions
- horizontal scaling is easier (any server can verify the token)
- CORS is not an issue if
Authorization
header is used instead ofCookie
- FE and BE architecture is decoupled, can be used with mobile apps
- operational even if cookies are disabled
- server still has to maintain a blacklist of revoked tokens
- defeats the purpose of stateless tokens
- a whitelist of active user sessions is more secure
- when scaling, the secret must be shared between servers
- data stored in token is "cached" and can go stale (out of sync)
- tokens stored in client storage are vulnerable to XSS
- if JWT token is compromised, attacker can
- steal user info, permissions, metadata, etc.
- access website resources on user's behalf
- if JWT token is compromised, attacker can
- requires JavaScript to be enabled
- Sessions
- Stateless JWT
- Stateful JWT
- user payload embedded in the token
- token is signed &
base64url
encoded- sent via
Authorization
header - stored in
localStorage
/sessionStorage
(in plaintext)
- sent via
- server retrieves user info from the token
- no user sessions are stored server side
- only revoked tokens are persisted
- refresh token sent to renew the access token
- only user ref (e.g. ID) embedded in the token
- token is signed &
base64url
encoded- sent as an HTTP-only cookie (
Set-Cookie
header) - sent along with non-HTTP
X-CSRF-TOKEN
cookie
- sent as an HTTP-only cookie (
- server uses ref. (ID) in the token to retrieve user from the DB
- no user sessions stored on the server either
- revoked tokens still have to be persisted
- sessions are persisted server-side and linked by sess. ID
- session ID is signed and stored in a cookie
- sent via
Set-Cookie
header HttpOnly
,Secure
, &SameSite
flags- scoped to the origin with
Domain
&Path
attrs
- sent via
- another cookie can hold CSRF token
Sessions are (probably) better suited for web apps and websites.
- server state needs to be maintained either way
- sessions are easily extended or invalidated
- data is secured server side & doesn't leak through XSS
- CSRF is easier to mitigate than XSS (still a concern)
- data never goes stale (always in sync with DB)
- sessions are generally easier to set up & manage
- most apps/sites don't require enterprise scaling
Regardless of auth mechanism
- XSS can compromise user accounts
- by leaking tokens from
localStorage
- via AJAX requests with user token in
Authorization
- via AJAX requests with
HttpOnly
cookies
- by leaking tokens from
- SSL/HTTPS must be configured
- security headers must be set
- IP verification
- user agent verification
- two-factor auth
- API throttling
In my projects I essentially find useful two ways to encrypt strings: hash functions one-way and one-way and encryption-decryption two-way :
Hash functions are essentials for store encrypted password, and the best library for nodejs is Bcrypt. You can find more information in this article: why use Bcrypt?.
Install:
npm install bcrypt
To hash a password:
const bcrypt = require('bcrypt');
const saltRounds = 10;
const myPlaintextPassword = 'myPassword';
bcrypt.hash(myPlaintextPassword, saltRounds).then(function(hash) {
// Store hash in your password DB.
});
At user login to compare password with the one stored in the db you can use:
bcrypt.compare(plaintextPassToCheck, hashStoredInDB).then(function(res) {
// res == true/false
});
More info: github.com/kelektiv/node.bcrypt.js
In other scenarios I needed to crypt strings in order to hide texts to users but in a way that allows me to decrypt and retrieve the original content. In this case a fast tool is Crypto.
Install:
npm install crypto
To encrypt and decrypt a string:
var crypto = require('crypto');
var cypherKey = "mySecretKey";
function encrypt(text){
var cipher = crypto.createCipher('aes-256-cbc', cypherKey)
var crypted = cipher.update(text,'utf8','hex')
crypted += cipher.final('hex');
return crypted; //94grt976c099df25794bf9ccb85bea72
}
function decrypt(text){
var decipher = crypto.createDecipher('aes-256-cbc',cypherKey)
var dec = decipher.update(text,'hex','utf8')
dec += decipher.final('utf8');
return dec; //myPlainText
}
A table that shows the error codes and their respective messages.
code | Msg |
---|---|
100 | username or Email already registered |
101 | Invalid email |
102 | Username may only contain alphanumeric characters or single hyphens, and cannot begin or end with a hyphen. |
103 | Invalid email |
104 | Not registered user! |
105 | Email or password incorrect |
106 | User successfully registered |
Licensed under the MIT license.