DEV Community

Cover image for APIs and Security Best Practices: JavaScript and Python Examples
Nathan Katshi
Nathan Katshi

Posted on

APIs and Security Best Practices: JavaScript and Python Examples

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',
  },
});
Enter fullscreen mode Exit fullscreen mode
  • Python
import requests

response = requests.get('https://api.example.com/data')
print(response.status_code)
Enter fullscreen mode Exit fullscreen mode

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}`,
  },
});
Enter fullscreen mode Exit fullscreen mode
  • 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)
Enter fullscreen mode Exit fullscreen mode

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}`,
  },
});
Enter fullscreen mode Exit fullscreen mode
  • Python
import requests

response = requests.get('https://api.example.com/data', headers={'Authorization': f'Bearer {access_token}'})
print(response.status_code)
Enter fullscreen mode Exit fullscreen mode

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: ""
Enter fullscreen mode Exit fullscreen mode

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: ""
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode
  • 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"
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode
  • 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
Enter fullscreen mode Exit fullscreen mode

b. Install the dotenv package

pip3 install python-dotenv
Enter fullscreen mode Exit fullscreen mode

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())
Enter fullscreen mode Exit fullscreen mode

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());
Enter fullscreen mode Exit fullscreen mode
  • Python
from flask_cors import CORS

app = Flask(__name__)
CORS(app)
Enter fullscreen mode Exit fullscreen mode

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)