Table Of Contents
- Project Structure
- Authentication Level Zero
- Authentication Level One
- Authentication Level One Advantages and Disadvantages
- References
When you hear or see the word authentication you relate it to identification and that is a correct approach to this term. We just need to add permissions to perform a specific action in an app and we will have a more complete definition for the technology area.
In this project we are going to have a series of authentication systems in order to better understand what they need, how they are built and how they work.
Of course being these kind of security systems, they need something to protect, in this occasion we established a list of Pokémon cards with which the user will get the permission to see them and interact with the search engine to sort them in order of pokemon type or by the name of the pokemon.
For this post we will cover the first authentication structure. So let's get started
Project Structure
We will use two repositories, one to manage the visual part, user interface and forms with technologies such as React for the development of interfaces, Sass to style those interfaces and Webpack to compile the application in a lighter and more adaptable format for browsers.
The second repository will be used to handle requests, database queries and information transformation. We will use technologies such as Node to be able to handle JavaScript from the Backend side, Express to create endpoints faster and PostgreSQL to handle the connection to this type of database and queries.
Finally we have the servers these would be the platforms, Vercel to host both parts working and ElephantSQL that offers PostgreSQL databases ideal for projects.
Authentication Level Zero
In order to understand how the project would be if it did not have authentications, this section is created where it is simulated that the data is exposed and any user can manipulate it without having permissions to do so.
It is also a quick way to know what we are protecting, a list of cards of the first generation Pokémons. By fetching the information from PokéAPI, we get pokémons with their name, types, an image that represents them and their identifier as a label.
Authentication Level One
For this first level we are going to build a simple login with username and password without email verification. In order to understand how it would be useful to us and what disadvantages it presents.
This will start to work the moment the user fills the form for the creation of an account correctly and presses the create account button. This will send a request to the Backend, to confirm if the data is correct, mainly that all the data is complete.
If so, the password is encrypted first, then the new user's information is written into the database, and then a correct response is sent to the Frontend to redirect the user to the login form.
This can be seen in the following diagram:
Here I share the function that handles the endpoint (file UsersController.js):
class UsersController{
async createUser(req, res){
const {body: user} = req;
try {
const createdUser = await usersService.createUser({ user });
res.status(201).json({
message: 'User created',
user: createdUser
});
} catch (err) {
console.log(err);
}
}
}
And this is the function in the Backend, where we verify the table fields, add a unique identifier and encrypt the password before writing the information (file UsersService.js):
const { client } = require('../../config/database');
const { v4: uuid } = require('uuid');
const bcrypt = require('bcrypt');
class UsersService {
constructor(){
this.table = 'users',
this.fields = 'id, username, password, email'
}
async createUser({ user }){
const { username, password, email, fullName } = user
try {
const id = uuid();
const encriptedPassword = await bcrypt.hash(password, 10);
const lowerCaseEmail = email.toLowerCase();
const userCreated = await client.query(
`INSERT INTO ${this.table}(${this.fields}) VALUES (
'${id}',
'${username}',
'${encriptedPassword}',
'${lowerCaseEmail}',
)`
)
return userCreated.rowCount;
} catch (err) {
console.error(err);
}
}
}
For the login a very similar process is done, what varies is the Backend processing where the existence of that user is confirmed, the password is verified to be correct and if everything is OK a response is sent with a JSON Web Token.
This token will be stored locally in Frontend with the browser's window.localStorage.setItem() function so that it can be used in requests that require it as a value in a header.
Diagram of the login process:
Function that handles the login endpoint:
async loginUser (req, res){
const { user, password } = req.body;
try {
if(!user || !password) res.status(401).send('Invalid information');
let userData;
const userDataByUsername = await usersService.getUserByUsername({user});
if(userDataByUsername.length === 0) {
const userDataByEmail = await usersService.getUserByEmail({user});
if(userDataByEmail.length === 0) res.status(401).send('Invalid information');
userData = userDataByEmail;
} else {
userData = userDataByUsername;
};
const comparedPassword = await bcrypt.compare(password, userData.password);
if(!comparedPassword) res.status(401).send('Invalid information');
const token = jwtAuthenticationService.JWTIssuer({user: userData.id}, '15 min');
res.status(200).json({ token: token })
} catch (err) {
console.log(err)
}
}
Function to consult users by username:
async getUserByUsername({ user }){
try {
const userData = await client.query(`SELECT * FROM ${this.table} WHERE username='${user}'`)
return userData.rows[0] || [];
} catch (err) {
console.error(err)
}
}
Function to consult users by email:
async getUserByEmail({ user }){
try {
const lowerCaseEmail = user.toLowerCase()
const userData = await client.query(`SELECT * FROM ${this.table} WHERE email='${lowerCaseEmail}'`)
return userData.rows[0] || [];
} catch (err) {
console.error(err)
}
}
Finally, the last thing that happens is that Frontend performs a query using the token to bring the user information and display the username.
This is the function that takes care of this endpoint:
async listUserById(req, res){
const { bearertoken } = req.headers;
if(!bearertoken) res.status(401).json({message: 'Request without token'})
const tokenData = await jwtAuthenticationService.JWTVerify(bearertoken)
if(tokenData === undefined) res.status(401).json({message: 'Invalid token'})
const userId = tokenData.user;
try {
const userData = await usersService.getUserById({ userId });
res.status(200).json({
message: 'User listed',
user: {
id: userData.id,
username: userData.username,
email: userData.email,
}
})
} catch (err) {
console.log('listUserById error: ', err);
}
}
Authentication Level One Advantages and Disadvantages
Advantages
- Easy to implement in any application
- Quick way to create users and be able to relate them to the other services of the application.
- Gradually more verifications and safety elements can be added.
Disadvantages
- It has a low level of security compared to other authentication structures.
- In case of password loss, it is necessary to contact support directly to change the password.
- If maintained in this manner without implementing further security measures, there is a risk of being breached.
If you noticed the account creation endpoint has no user verification so someone can create an account with the same email and username without any restriction.
How did we prevent this situation from happening? Share your answer in the comments
Lastly, now that you know the functionality of this application I invite you to review it, try it and leave me your suggestions to improve it.
- App Demo: https://frontend-bas-ulzahk.vercel.app/
If you want to review the application documentation, here I share the repositories:
- App Repository: https://github.com/Ulzahk/Frontend-BAS
- API Repository: https://github.com/Ulzahk/Backend-BAS
References
- PokéAPI: https://pokeapi.co/
- ElephantSQL: https://www.elephantsql.com/
- JSON Web Token: https://jwt.io/
- bcrypt for NodeJs: https://www.npmjs.com/package/bcrypt
Top comments (2)
You should use parameters on the queries, not generate the query text. You're vulnerable to SQL injection in the user service.
Thanks Robert, I will keep this in mind for future updates.