As we close the Cybersecurity Awareness Month, it is crucial to underscore the importance of secure coding practices. In this article, I highlight some basic but often overlooked API security best practices, backed by examples in JavaScript and Python.
APIs Overview
An API (Application Programming Interface) is a set of protocols and rules that allow different software applications to communicate and exchange data with each other. APIs enable seamless integration of various systems.
Think of an API like a waiter in a restaurant. Just as a waiter takes your order, delivers it to the kitchen, and brings your meal back to you, an API takes a request from one application, sends it to the server, and returns the server’s response to the requesting application.
Different types of API exist, based on their architectures and use cases. Some common API types are:
REST (Representational State Transfer): REST APIs are based on standard HTTP protocols and are widely used for many web services. They offer simplicity and ease of use, making them popular for many programs.
GraphQL (Graph Query Language): GraphQL APIs allow clients to request exactly the data they need, making data retrieval more efficient. It’s a query language allowing for more flexible data interactions.
SOAP (Simple Object Access Protocol): SOAP APIs are protocol-based and use XML for messaging. They provide a more rigid structure and are often used in enterprise-level applications.
RPC (Remote Procedure Call): RPC APIs allow a client to execute code on a remote server. This technique is often used to execute functions over the network.
Risk linked to the Use of APIs
Using APIs can expose countless security risks, especially if they are not properly implemented and managed.
Some of the risks are broken object-level authorization and user authorization, excessive data exposure, lack of resources and rate limiting, and insufficient logging and monitoring.
Addressing these risks involves implementing robust security measures.
API Security Best Practices
API security measures can be implemented in various ways, depending on API use and application requirements. The measures implemented are language-independent.
Core concepts are the same, but the syntax may slightly differ.
In the examples below, we will use the URL https://api.example.com/data
1. Use HTTPS over HTTP
Using the secure version of the HTTP protocol ensures data transmitted between the client and server are kept private and protected against eavesdropping and man-in-the-middle attacks.
- JavaScript (React)
// The API requests use HTTPS
fetch('https://api.example.com/data', {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
});
- Python
import requests
response = requests.get('https://api.example.com/data')
print(response.status_code)
2. Use API Keys in HTTP Headers
API keys are useful for authenticating and authorizing API requests, ensuring that only legitimate clients can access the API data.
- JavaScript (React)
fetch('https://api.example.com/data', {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${apiKey}`,
},
});
- Python
import requests
headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {api_key}'
}
response = requests.get('https://api.example.com/data', headers=headers)
print(response.status_code)
3. Implement OAuth 2.0
OAuth 2.0 provides secure and scalable authorization for accessing resources, allowing third-party applications to access user data without exposing credentials.
- JavaScript (React)
import axios from 'axios';
axios.get('https://api.example.com/data', {
headers: {
'Authorization': `Bearer ${accessToken}`,
},
});
- Python
import requests
response = requests.get('https://api.example.com/data', headers={'Authorization': f'Bearer {access_token}'})
print(response.status_code)
4. Implement Input Validation
Proper input validation ensures that only properly formatted data is accepted by the API, protecting against injection attacks and malformed input.
In the examples below, I use the DOMPurify
library, which is one of the most powerful and widely trusted in the development community for its robust and reliable features, notably for preventing XSS (Cross-Site Scripting) attacks.
- JavaScript
import DOMPurify from 'dompurify';
function sanitizeInput(input) {
return DOMPurify.sanitize(input);
}
const userInput = '<script>alert("XSS Attack!")</script>';
const sanitizedInput = sanitizeInput(userInput);
console.log(sanitizedInput); // Outputs: ""
In this example, DOMPurify
removes the <script>
tag from the user input, making it safe to use in your application.
While DOMPurify
is specifically for JavaScript, similar functionality can be achieved in Python using libraries like Bleach
- Python
import bleach
def sanitize_input(input):
return bleach.clean(input)
user_input = '<script>alert("XSS Attack!")</script>'
sanitized_input = sanitize_input(user_input)
print(sanitized_input) # Outputs: ""
5. Use Rate Limiting
Rate limiting controls the number of requests a client can make to an API within a specified time frame, protecting against DoS(Denial of Service) attacks.
- JavaScript (Express)
const rateLimit = require('express-rate-limit');
const apiLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // limit each IP to 100 requests per windowMs
});
app.use('/api/', apiLimiter);
- Python (Flask)
from flask import Flask
from flask_limiter import Limiter
from flask_limiter.util import get_remote_address
app = Flask(__name__)
limiter = Limiter(app, key_func=get_remote_address)
@app.route('/api/data')
@limiter.limit("100 per 15 minutes")
def get_data():
return "Data"
6. Secure Sensitive Data
Securing sensitive data ensures data are stored and transmitted securely, and protected from unauthorized access.
One of the techniques consists of storing API keys in an environment variable to avoid exposing it in the source code.
- JavaScript (React)
a. Create a .env
file in the root folder
API_KEY=your_api_key_here
b. Use the API key in the code
import React from 'react';
function fetchData() {
const apiKey = process.env.API_KEY;
fetch('https://api.example.com/data', {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${apiKey}`,
},
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));
}
# Rest of the code here
-
Python
In Python, you can use the
dotenv
package to load the API key from an environment variable.
a. Create a .env
file
API_KEY=your_api_key_here
b. Install the dotenv
package
pip3 install python-dotenv
c. Use the API key in the code
import os
from dotenv import load_dotenv
import requests
load_dotenv()
api_key = os.getenv('API_KEY')
headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {api_key}'
}
response = requests.get('https://api.example.com/data', headers=headers)
print(response.json())
7. Enable Cross-Origin Resource Sharing Properly
CORS (Cross-Origin Resource Sharing) allows you to control which domains can access your API, protecting against unauthorized cross-origin requests.
- JavaScript (React)
const cors = require('cors');
app.use(cors());
- Python
from flask_cors import CORS
app = Flask(__name__)
CORS(app)
Summary
Insecurity costs more than security.
By combining some of these security measures, you will improve the overall application robustness and prevent various potential threats. Implementing HTTPS, API keys, OAuth 2.0, input validation, rate limiting, secure data handling, and proper CORS configuration are all critical steps toward safeguarding your APIs.
Ensuring that these measures are in place will not only protect your data and systems but also instill confidence in your users about the security of your applications.
Top comments (0)