Durojaye Olusegun
10 min readFeb 24, 2023

API SECURITY- A BEGINNERS GUIDE

Photo by securitymagazine

In this Blog, we would discuss what is an APIs, various security issues associated with API and basic illustrations on how to secure APIs.

Introduction to API Security

APIs (Application Programming Interfaces) are the building blocks of modern software. They enable the creation of powerful applications that can be integrated into other systems and services. APIs allow developers to create applications that leverage data, functionality and services provided by other software components.

APIs have become an essential component of many modern software systems in today’s digital world. Most companies now offer some form of API as part of their business strategy, enabling them to integrate third-party systems and services with their own applications. This enable software components to communicate and exchange data with one another, allowing the integration of various systems and services. However, as the use of APIs has grown, security has become a serious worry for many organizations.

Attackers frequently target APIs because they allow direct access to important data and functionality. API security entails safeguarding APIs against a variety of risks, including unauthorized access, data theft, and denial-of-service assaults. In this blog, we’ll look at API security and discuss some recommended practices for securing APIs.

</> Authentication and Authorization

Authentication is the process of verifying the identity of a user or program seeking to access an API. Authorization, on the other hand is the action that a user or application is permitted to take after being authenticated. To guarantee that only authorized users or apps can use an API, these two security techniques are crucial. Several techniques, including API keys, OAuth, and JSON Web Tokens, can be used by API providers to enable authentication and authorization (JWTs). It’s critical to pick an authentication and authorization solution that meets your organization’s requirements and provides a high level of security.

Based on this, let’s consider a case study for implementing authentication and authorization for a simple REST API using Node.js and Express.

First, we need to install dependencies:

npm install express jsonwebtoken body-parser

Then, we can define our API endpoint:

const express = require('express');
const jwt = require('jsonwebtoken');
const bodyParser = require('body-parser');

const app = express();

const users = [
{ id: 1, name: 'John Doe', role: 'admin' },
{ id: 2, name: 'Jane Smith', role: 'user' },
{ id: 3, name: 'Bob Johnson', role: 'user' }
];

const secretKey = 'mysecretkey';

app.use(bodyParser.json());

// Public endpoint to get a JWT token
app.post('/api/authenticate', (req, res) => {
const { username, password } = req.body;

// Check if username and password are valid
if (username === 'admin' && password === 'admin') {
const token = jwt.sign({ role: 'admin' }, secretKey);
res.json({ token });
} else {
res.status(401).send('Unauthorized');
}
});

// Protected endpoint to get a list of users
app.get('/api/users', (req, res) => {
const token = req.headers.authorization;

// Check if token is valid
try {
const decodedToken = jwt.verify(token, secretKey);
if (decodedToken.role === 'admin') {
res.json(users);
} else {
res.status(401).send('Unauthorized');
}
} catch (err) {
res.status(401).send('Unauthorized');
}
});

app.listen(3000, () => console.log('Server running on port 3000'));

The public API endpoint /api/authenticate accepts a username and password, and if they are valid. it returns a JWT token that has been encrypted with our secret Key.

The /api/users endpoint is a protected endpoint that requires a valid JWT token in the Authorization header to access. If the token is valid and has the role of 'admin', it returns the list of users. Otherwise, it returns an 'Unauthorized' response.

This is a fairly simple illustration, and while implementing API authentication and permission, there are many more security issues to keep in mind. But, this should give you a general notion of how to begin using Node.js and Express to secure your APIs.

</> Rate Limiting

Rate Limiting is a technique used to limit the number of API request that can be sent in a given period of time. By limiting the number of requests, Rate limit prevents attacks that could overload the API and result in denial-of-service(DOS) issues. Rate limiting can also help protect against brute-force attacks, where an attacker attempts to guess usernames and passwords through multiple API requests.

Based on this, let’s consider a case study for implementing rate limiting. This time around we would use python to illustrate this case study using Flask:

from flask import Flask, jsonify
from flask_limiter import Limiter
from flask_limiter.util import get_remote_address

app = Flask(__name__)

users = [
{ 'id': 1, 'name': 'John Doe' },
{ 'id': 2, 'name': 'Jane Smith' },
{ 'id': 3, 'name': 'Bob Johnson' }
]

limiter = Limiter(
app,
key_func=get_remote_address,
default_limits=["10 per minute"]
)

@app.route('/api/users')
@limiter.limit("1/minute")
def get_users():
return jsonify(users)

if __name__ == '__main__':
app.run(debug=True)

we use the Flask framework and the flask-limiter library to implement rate limiting for our API. We create a limiter object that limits each IP address to 10 requests per minute by default. We then use the @limiter.limit() decorator to apply an additional rate limit of 1 request per minute to the /api/users endpoint. We define our /api/users endpoint that returns a list of users using the jsonify() method from Flask to convert the users list to a JSON response.

There are numerous more options and factors to take into account when setting rate restriction for APIs; this code is but one simple example. To implement rate limitation in your own Python Flask application, however, this ought to provide you with a decent starting point.

</> Input Validation

Input Validation is the process of ensuring data sent to an API is valid and adhere to specific format. This can help prevent attacks, such as cross-site scripting (XSS) and SQL injection, where attackers exploit vulnerabilities in the API to inject malicious code or steal data.

It is important for API providers to implement input validation mechanism to ensure that the data recieved by API is valid and meet the required format.

here’s an example of input validation for an API endpoint using the Flask framework in Python:

from flask import Flask, request, jsonify
import re

app = Flask(__name__)

@app.route('/api/user', methods=['POST'])
def create_user():
data = request.get_json()

# Validate input fields
if 'name' not in data:
return jsonify({'error': 'Name is required'}), 400
if 'email' not in data:
return jsonify({'error': 'Email is required'}), 400
if not re.match(r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$', data['email']):
return jsonify({'error': 'Invalid email format'}), 400

# Save user data to database
# ...

return jsonify({'message': 'User created successfully'}), 201

if __name__ == '__main__':
app.run(debug=True)

In this example, we have an API endpoint /api/user that accepts POST requests to create a new user. The request body should contain a JSON object with a name and email field. We first retrieve the JSON data using request.get_json(). We then validate the input fields using conditional statements. If the name or email fields are missing, we return a 400 Bad Request response with an error message.

We also use a regular expression to validate the format of the email address. In this case, we use a simple regular expression that checks for a basic email format with a username, @ symbol, domain name, and top-level domain (e.g. example@domain.com). If all input fields are valid, we can then save the user data to a database or perform other operations as needed. Finally, we return a 201 Created response with a success message.

This is only a straightforward illustration of input validation for API security. Depending on the demands of your API and the sensitivity of the data you are managing, you might need to undertake more thorough validation and sanitization of input data in practice.

</> Encryption

Encryption is the process of converting data into a code to prevent unauthorized access. APIs that handle sensitive data, such as personal information and financial data, should be encrypted to prevent data theft so even if there is a data breach, the data stolen would be obfusticated. Encryption can be implemented using different encryption algorithms, such as Advanced Encryption Standard (AES) and Secure Sockets Layer (SSL).

In this blog, we would look at use case of Advanced Encryption Standard (AES). Assuming we have a user registration form that accepts sensitive user data like passwords and credit card details, we must ensure that this information is safely kept in our database. In the event that our database is compromised, we can use encryption to protect this data.

First, we need to install the cryptography library:

pip install cryptography

Then, we can define a function that encrypts the sensitive user data using the Advanced Encryption Standard (AES) algorithm:

from cryptography.fernet import Fernet

# Generate a new encryption key
key = Fernet.generate_key()

def encrypt_data(data):
# Create a new Fernet instance using the encryption key
fernet = Fernet(key)

# Encrypt the data
encrypted_data = fernet.encrypt(data.encode())

# Return the encrypted data
return encrypted_data

In this code, we generate a new encryption key using the Fernet.generate_key() method. We then define a encrypt_data() function that takes in sensitive user data as a parameter, creates a new Fernet instance using the encryption key, and encrypts the data using the fernet.encrypt() method. The encrypted data is then returned.

To decrypt the encrypted data, we can define another function:

def decrypt_data(encrypted_data):
# Create a new Fernet instance using the encryption key
fernet = Fernet(key)

# Decrypt the data
decrypted_data = fernet.decrypt(encrypted_data)

# Return the decrypted data
return decrypted_data.decode()

This function takes in the encrypted data as a parameter, creates a new Fernet instance using the encryption key, and decrypts the data using the fernet.decrypt() method. The decrypted data is then returned as a string.

In our user registration form, we can call the encrypt_data() function to encrypt the sensitive user data before storing it in our database:

from flask import Flask, request
import sqlite3

app = Flask(__name__)

@app.route('/register', methods=['POST'])
def register():
# Get the user data from the registration form
username = request.form['username']
password = request.form['password']
credit_card_number = request.form['credit_card_number']

# Encrypt the sensitive user data
encrypted_password = encrypt_data(password)
encrypted_credit_card_number = encrypt_data(credit_card_number)

# Store the encrypted user data in the database
conn = sqlite3.connect('users.db')
c = conn.cursor()
c.execute('INSERT INTO users (username, password, credit_card_number) VALUES (?, ?, ?)', (username, encrypted_password, encrypted_credit_card_number))
conn.commit()
conn.close()

return 'Registration successful'

In this code, we define a Flask route for the user registration form that accepts the user’s username, password, and credit card number. We call the encrypt_data() function to encrypt the password and credit card number before storing them in our SQLite database.

To decrypt the sensitive user data, we can call the decrypt_data() function when retrieving the data from the database:

@app.route('/login', methods=['POST'])
def login():
# Get the user data from the login form
username = request.form['username']
password = request.form['password']

# Retrieve the user data from the database
conn = sqlite3.connect('users.db')
c = conn.cursor()
c.execute('SELECT * FROM users WHERE username = ?', (username,))
row = c.fetchone()

</>Monitoring and Logging

API providers should monitor their APIs regularly to identify any suspicious activity, such as unusual patterns of requests, failed authentication attempts, or unauthorized access. API providers should also log all API activity, including authentication and authorization attempts, API requests, and API responses. Monitoring and logging can help detect and respond to security incidents quickly and prevent potential attacks.

let’s look at an example of monitoring and logging for an API endpoint using the flask framework in python:

from flask import Flask, request, jsonify
import logging

app = Flask(__name__)

# Set up logging
logging.basicConfig(filename='api.log', level=logging.INFO)

@app.route('/api/user', methods=['POST'])
def create_user():
data = request.get_json()

# Log incoming request
logging.info(f'Received request to create user: {data}')

# Validate input fields
if 'name' not in data:
logging.warning('Name is missing from request')
return jsonify({'error': 'Name is required'}), 400
if 'email' not in data:
logging.warning('Email is missing from request')
return jsonify({'error': 'Email is required'}), 400

# Save user data to database
# ...

# Log successful response
logging.info('User created successfully')

return jsonify({'message': 'User created successfully'}), 201

if __name__ == '__main__':
app.run(debug=True)

In this example, we have an API endpoint /api/user that accepts POST requests to create a new user. The request body should contain a JSON object with a name and email field.

We first set up logging using the built-in Python logging library. We configure a file handler to write logs to a file named api.log, and set the logging level to INFO.

Inside the create_user function, we log the incoming request using logging.info(). This can be helpful for tracking the frequency and content of API requests, as well as for identifying any potential security threats or vulnerabilities.

We then perform input validation and logging for any errors or warnings using logging.warning(). This can help us identify any malformed or suspicious requests, and take action as needed.

Finally, we log a success message and return a response to the client. By logging all incoming requests and responses, we can build a detailed record of API activity and security events, which can be useful for monitoring and debugging.

Again, this is just a basic example of monitoring and logging for API security. In practice, you may need to implement more extensive logging and monitoring measures, depending on the requirements of your API and the sensitivity of the data you are handling.

The idea is to introduce you to API security and pass a basic knowledge of securing API endpoint in a web Application.

Conclusion

API security is a critical aspect of modern software systems, and organizations should take steps to ensure that their APIs are secure. By implementing authentication and authorization, rate limiting, encryption, input validation, and monitoring and logging, organizations can protect their APIs against a range of threats and ensure the confidentiality, integrity, and availability of their data and services.

Stay connected and subscribe to this blog, In our upcoming blog we would discuss more about cybersecurity.

Thank you for taking the time to read my blog; your attention and support are greatly appreciated. It is my desire to share my ideas and thoughts with you, and I hope you find my content interesting and informative..

If you enjoyed reading my blog, I encourage you to subscribe to receive future updates. By subscribing, you’ll never miss a post and you’ll be the first to know about my latest content.

Here is my Github repo:

My twitter:

My Linkedin:

https://www.linkedin.com/in/durojaye-olusegun-7a5023190/

Durojaye Olusegun

I’m a Technical writer and Researcher sharing insights on General Information Security, Data solutions, Generative AI and ML