Securing APIs - Coding Ninjas
Securing APIs - Coding Ninjas
Understanding Security
Types of Authentication
User APIs
var users = [
{
id: 1,
name: 'Seller User',
email: 'seller@ecom.com',
password: 'Password1',
type: 'seller',
},
];
● The user.controller.js file is created with a controller class that includes two
functions: signUp and signIn, which both take request and response
parameters.
import UserModel from './user.model.js';
● In the user.routes.js file, create routes for user sign-up and sign-in.
● The routes for sign-up and sign-in are both POST requests.
// Manage routes/paths to ProductController
// 1. Import express.
import express from 'express';
import UserController from './user.controller.js';
userRouter.post('/signup', userController.signUp);
userRouter.post('/signin', userController.signIn);
● Test the sign-in API. The request body for sign-in only requires the user's
email and password.
● Provide a valid email and password, and send the request. The response
confirms a successful login with the message "Login successful."
● If incorrect credentials are provided, the response will indicate a bad request
with the message "Incorrect credentials."
● It is noted that although the sign-up and sign-in APIs are functioning, the
application is not yet secure. Access to other APIs is possible without
authentication.
● Simply exposing the login API does not make the application secure. Each
request needs to be validated to ensure the user is logged in.
Basic Authentication
1. Objective: Implement basic authentication to secure the APIs in the application.
2. Basic authentication mechanism:
● Credentials (username and password) provided by the client will be checked
on each request.
● Middleware named "basicAuth" will be used to simplify the authentication
process and ensure data validation against attacks like SQL injections.
● The "express-basic-auth" package will be installed to set up basic
authentication.
3. Middleware Implementation:
● Create a middleware named "basicAuth" in the "middlewares" folder.
● The middleware will compare the received email and password with the user
data stored in the user model.
● If the credentials do not match, an error will be returned.
● If the credentials are correct, the middleware will proceed to the next
middleware.
● Secure string comparison will be performed using the "safeCompare" function
to protect against timing attacks.
● Code Implementation:
import bAuth from 'express-basic-auth';
import UserModel from '../features/user/user.model.js';
const basicAuthorizer = (username, password) => {
// 1. Get users
const users = UserModel.getAll();
// 2. Compare email
const user = users.find((u) =>
bAuth.safeCompare(username, u.email)
);
if (user) {
// 3. Compare password and return
return bAuth.safeCompare(
password,
user.password
);
} else {
// 4. Return error message
return res.status(401).send('Unauthorized');
}
};
5. Usage:
● Import the "authorizer" middleware.
● Apply the "authorizer" middleware to the relevant API routes that require
authentication (e.g., products APIs) to enforce authentication for accessing
those routes.
server.use(
'/api/products',
authorizer,
productRouter
);
Testing Basic Authentication
● Start the server using the command "node server".
● Use Postman for API testing. The "API products" request is selected, which
has been secured by the authorizers.
● Accessing the API without providing any credentials results in a 401
unauthorized response, indicating the lack of authorization.
Understanding JWT
● Sensitive data should not be stored in the payload but mentions that user
permissions are a suitable example of data to include.
● JWT is recommended because it provides a stateless behavior of
authentication, allowing the server to verify the token without needing to store
client information.
● Visit the jwt.io website to explore the details and examples of JWT tokens.
● Process of using JWT : It involves the client logging in with their credentials
through the login API, the server verifying the credentials and generating a
JWT token, and then sending the token back to the client. The client stores
the token, typically in the browser, and includes it in the authorization header
when making requests to secure APIs.
The server verifies the token and provides a response if the verification is
successful. If the token has been modified or expired, an error with the status
code 401 is returned.
JWT Authentication - 1
signIn(req, res) {
const result = UserModel.signIn(
req.body.email,
req.body.password
);
if (!result) {
return res
.status(400)
.send('Incorrect Credentials');
} else {
// 1. Create token.
const token = jwt.sign(
{
userID: result.id,
email: result.email,
},
'AIb6d35fvJM4O9pXqXQNla2jBCH9kuLz',
{
expiresIn: '1h',
}
);
// 2. Send token.
return res.status(200).send(token);
}
}
}
● The payload should not contain sensitive data like passwords but can include
information such as user ID and authorization permissions.
● The user ID is stored in the payload using the value result.id.
● The private key, which is used for signing and verifying the token, should be a
strong and secure key. Online key generators are recommended for
generating such keys.
● Set the "expiresIn" option to one hour, indicating that the token will be invalid
after that time.
● After creating the token, return it to the client with a status code of 200 (OK).
● The modified login API now generates a token and sends it back to the client.
● The token serves as the client's validation key and can be used to access
secure routes.
JWT Authentication - 2
● Create a new middleware called "jwt.middleware.js"
● Define the middleware function with the parameters: request, response, and
next.
● Reading the token:
- Retrieve the token from the client.
- Check if the token exists; if not, return an error message (unauthorized
access) with a status code of 401.
● Checking token validity:
- Import the JSON Web Token (JWT) library.
- Use the JWT library's "verify" function to check if the token is valid.
- Pass the token and the signing key used during token creation to the
"verify" function.
- Wrap the verification process in a try-catch block to handle errors.
- If any error occurs during verification, return an unauthorized access
error (status code 401).
- If the verification is successful, retrieve the payload from the verified
token.
- Print the payload for testing purposes.
- If the token is present, not empty, and valid, call the next middleware in
the pipeline.
- Export the JWT Auth middleware to be used in securing specific
routes.
- Apply the JWT middleware to secure routes.
- Replace the existing authorizer with the JWT Auth middleware.
- jwt.middleware.js file
import jwt from 'jsonwebtoken';
console.log(token);
// 2. if no token, return the error.
if (!token) {
return res.status(401).send('Unauthorized');
}
// 3. check if token is valid.
try {
const payload = jwt.verify(
token,
'AIb6d35fvJM4O9pXqXQNla2jBCH9kuLz'
);
console.log(payload);
} catch (err) {
// 4. return error.
console.log(err);
return res.status(401).send('Unauthorized');
}
// 5. call next middleware
next();
};
Summarising it
Let’s summarise what we have learned in this module:
● Created user API for user registration and login, including sign-up and
sign-in functionalities.