How to create a great API a guide for functional performance security

Mar 7, 2024 | by Ralph Van Der Horst

How to Create a Great API A Guide for functional performance security

How to Create a Great API: A Guide for anybody interested

Why creating APIS, The Importance of APIs

An API (Application Programming Interface) acts as a menu for software, detailing available functions and how they can interact. It’s essential for enabling different software systems to communicate smoothly and efficiently. A well-designed API can significantly enhance the functionality and value of software applications by allowing them to leverage external services or data seamlessly.

Are you ready to make your very own API, but not sure where to start? Don’t worry! We’ve got you covered with some simple steps and examples to help you build a fantastic API that is secure, fast, and easy to use so it can be properly developed and tested

Step 1: Planning Your API functionaly

  • Consistency: Your API should be easy to read and understand. Use clear naming conventions and maintain a consistent format across your API to avoid confusion.
  • Appropriate Actions: Utilize HTTP verbs (GET, POST, PUT) appropriately to organize your API’s functionality logically.
  • Comprehensive Coverage: Ensure your API covers all the actions users might need, making it truly useful.
  • Data Integrity and Security: Be clear and consistent in the data your API requires and provides, and ensure you’re not exposing sensitive information unnecessarily.
  • Clear Responses: Make sure your API gives clear feedback, letting users know the outcome of their requests or why a request failed. Example: OpenAPI Specification

Shows a basic setup using the OpenAPI standard, which is easily understandable and helps ensure that your API is well-documented and testable.

An api set up can be generated in openapi standard so it is easily readable and good to understand

openapi: 3.0.3
info:
  title: SAP Accounts Payable API
  description: API for managing accounts payable, including vendor details and invoice entries.
  version: 1.0.0
servers:
  - url: http://sap.example.com/api/v1
paths:
  /vendors/{vendorId}:
    get:
      summary: Get vendor details
      operationId: getVendorById
      parameters:
        - name: vendorId
          in: path
          required: true
          description: Unique identifier of the vendor
          schema:
            type: string
      responses:
        '200':
          description: Successful operation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Vendor'
        '404':
          description: Vendor not found
  /invoices:
    post:
      summary: Create a new invoice entry
      operationId: createInvoice
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/NewInvoice'
      responses:
        '201':
          description: Invoice created successfully
        '400':
          description: Invalid input
components:
  schemas:
    Vendor:
      type: object
      properties:
        vendorId:
          type: string
          description: Unique identifier of the vendor
        name:
          type: string
          description: Name of the vendor
        email:
          type: string
          description: Email address of the vendor
    NewInvoice:
      type: object
      properties:
        vendorId:
          type: string
          description: Vendor associated with the invoice
        amount:
          type: number
          format: double
          description: Total amount of the invoice
        dueDate:
          type: string
          format: date
          description: Due date for the invoice payment

This example provides a basic OpenAPI specification for an SAP Accounts Payable API, which includes operations for managing vendor information and invoice entries. The contract testing examples demonstrate how to validate the API’s functionality through positive and negative tests, ensuring that the API behaves as expected under various conditions. This approach helps maintain the reliability and integrity of the API, which is crucial for financial operations like accounts payable management

Update Carefully: When you make changes to your API, do it in a way that doesn’t break it for users. You might have different versions (like v1, v2) for big changes, you can use pact for mock checking to avoid breaking changes in production. Pact I explained in another blog on my website

Give Helpful Errors: If something goes wrong, your API should explain what happened in a simple way. This helps users fix their requests.

How can you set create those funcional checks. By documenting and implicit validating the documentation carefully Functionally those errors should look like

  1. Validation Errors

When the user provides data that doesn’t meet the validation criteria (e.g., a required field is missing, or a value is out of the allowed range).

  • Bad Error Message: “Validation failed.”
  • Good Error Message: “Validation error: The ’email’ field is required and must be a valid email address.”
  1. Authentication and Authorization Errors

When a user is not authenticated (logged in) or does not have permission to access a certain resource or perform a certain action.

  • Bad Error Message: “Access denied.”
  • Good Error Message: “Authentication error: You must be logged in to access this resource.” or “Authorization error: You do not have permission to perform this action.”
  1. Resource Not Found

When the requested resource (e.g., a specific item or endpoint) does not exist.

  • Bad Error Message: “Not found.”
  • Good Error Message: “The requested item with ID 12345 was not found. Please ensure the ID is correct and try again.”
  1. Rate Limiting

When a user has made too many requests in a short period.

  • Bad Error Message: “Request limit exceeded.”
  • Good Error Message: “You have exceeded your request limit of 100 requests per hour. Please wait for some time before making new requests.”
  1. Internal Server Errors

When something goes wrong on the server-side that the user cannot directly fix.

  • Bad Error Message: “Internal server error.”
  • Good Error Message: “An unexpected error occurred on our end. We have been notified and are working on a fix. Please try again later.”
  1. Dependency Failures

When your API depends on external services or systems that are currently unavailable or failing.

  • Bad Error Message: “Service unavailable.”
  • Good Error Message: “Our currency conversion service is currently unavailable. We are aware of the issue and working to restore service as soon as possible. Please try again later.”
  1. Data Format Errors

When the format of the data provided (e.g., JSON, XML) is incorrect or malformed.

  • Bad Error Message: “Bad request.”

  • Good Error Message: “Your request body contains invalid JSON. Please correct it and try again.” For each type of error, it’s important to:

  • Clearly state what the issue is.

  • Provide guidance or suggestions for resolving the issue, if possible.

  • Use a polite and constructive tone. .

Step 2: Making Your API Secure and performant (NFR testing) so Fast

Security practice 1)Keep It Safe: Your API should check who is trying to use it. You can use passwords, tokens (like a secret key), or other methods to make sure only the right people have access.

components:
  securitySchemes:
    OAuth2:
      type: oauth2
      flows:
        authorizationCode:
          authorizationUrl: https://sap.finance.com/oauth2/authorize
          tokenUrl: https://sap.finance.com/oauth2/token
          scopes:
            read:invoices: "Read access to invoices"
            write:invoices: "Write access to invoices"

Or use basic auth (not preferred solution nowadays in prod but for testing fine)

  # requestBody and responses go here
import requests
from requests.auth import HTTPBasicAuth

# SAP service URL
url = 'http://sap.example.com/api/v1/invoices'

# Your credentials
username = 'your_username'
password = 'your_password'

# Make a GET request with Basic Authentication
response = requests.get(url, auth=HTTPBasicAuth(username, password))

# Check if the request was successful
if response.status_code == 200:
    # Process the response data (assuming JSON response)
    data = response.json()
    print(data)
else:
    print(f'Failed to retrieve data. Status code: {response.status_code}')
import requests

# The URL of the API you're calling
url = 'http://example.com/api/data'

# Your API key
api_key = 'your_api_key_here'

# The header where the API key is expected (commonly 'Authorization' or 'X-API-Key')
headers = {
    'Authorization': f'Bearer {api_key}',
    # or if the API expects a custom header, it might look like
    # 'X-API-Key': api_key
}

# Make the request
response = requests.get(url, headers=headers)

# Check if the request was successful
if response.status_code == 200:
    # Process the response data (assuming JSON response)
    data = response.json()
    print(data)
else:
    print(f'Failed to retrieve data. Status code: {response.status_code}')

Prevent Overload: Set limits on how often someone can ask your API for something. This helps keep your API working smoothly without crashing.

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

# Initialize Flask app
app = Flask(__name__)

# Initialize Limiter
limiter = Limiter(
    app,
    key_func=get_remote_address,  # Use the remote address of the client as the rate limiting key
    default_limits=["100 per hour", "20 per minute"]  # Global rate limits
)

# Apply rate limits to specific routes
@app.route("/api/data")
@limiter.limit("10 per minute")  # More restrictive rate limit for this endpoint
def get_data():
    return jsonify({"message": "This is rate limited API data."})

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

Performance practice 1) Rate Limit Strategy: The rate limits you set (“100 per hour”, “20 per minute”) should be based on your server’s capacity and the expected usage pattern of your API. You might need to adjust these values based on real-world usage data. It is possible to customizing Rate Limits: You can set rate limits to apply globally, per-route, or even dynamically based on the type of user making the request. Handling Rate Limit Exceeding: When a client exceeds the rate limit, usually it will return a 429 Too Many Requests response.

Security practice 2) Security and Fairness: While rate limiting is crucial for protecting your API, ensure that it’s implemented fairly and doesn’t inadvertently block legitimate users from accessing your service. Consider implementing more sophisticated rate limiting based on user accounts or API keys for finer control. So user b can use 200 request a second etc, or use geolocation setup

Performance practice 2) Caching: Think about storing some data temporarily so your API can give answers faster. This makes your API more efficient. Underneath a client side example

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Caching with localStorage</title>
</head>
<body>
    <button id="fetchDataBtn">Fetch Data</button>
    <div id="dataDisplay"></div>

    <script>
        const fetchDataBtn = document.getElementById('fetchDataBtn');
        const dataDisplay = document.getElementById('dataDisplay');

        const fetchData = async () => {
            const cacheKey = 'apiData';
            const cacheTimeKey = 'apiDataTime';
            const cacheDuration = 60000; // Cache duration in milliseconds (e.g., 60000ms = 1 minute)

            // Check if we have cached data and it's within our cache duration
            const cachedData = localStorage.getItem(cacheKey);
            const cachedTime = localStorage.getItem(cacheTimeKey);
            if (cachedData && cachedTime && new Date().getTime() - cachedTime < cacheDuration) {
                console.log('Using cached data');
                displayData(JSON.parse(cachedData));
                return;
            }

            console.log('Fetching new data');
            // Replace 'https://jsonplaceholder.typicode.com/posts' with the actual API URL
            const response = await fetch('https://jsonplaceholder.typicode.com/posts');
            if (!response.ok) {
                throw new Error(`API request failed with status ${response.status}`);
            }
            const data = await response.json();

            // Update cache with new data
            localStorage.setItem(cacheKey, JSON.stringify(data));
            localStorage.setItem(cacheTimeKey, new Date().getTime());

            displayData(data);
        };

        const displayData = (data) => {
            // Just display the first item for demonstration
            if (data && data.length > 0) {
                dataDisplay.innerText = `Post #1: ${data[0].title}`;
            } else {
                dataDisplay.innerText = 'No data found';
            }
        };

        fetchDataBtn.addEventListener('click', fetchData);
    </script>
</body>
</html>

Security practice 3) Sql injection example

from flask import Flask, request
import sqlite3

app = Flask(__name__)

@app.route('/api/search', methods=['GET'])
def search():
    keyword = request.args.get('keyword')  # User-provided search keyword

    # Dangerous: Constructing SQL directly from user input
    query = f"SELECT * FROM products WHERE name LIKE '%{keyword}%'"

    conn = sqlite3.connect('database.db')
    cursor = conn.cursor()
    cursor.execute(query)
    results = cursor.fetchall()

    return {'results': results}

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

Exploiting the Vulnerability:

An attacker might use the search feature to inject SQL commands by providing a specially crafted keyword:


%'; DROP TABLE products; -- 

When included in the API request, the constructed SQL query becomes:

SELECT * FROM products WHERE name LIKE '%%'; DROP TABLE products

cts; –%'

What tools can you use for testing functionality performance and security of apis

Functionality Testing

  • Postman: One of the most popular API testing tools, Postman is great for manual and automated tests. It allows for easy creation, sharing, and execution of API tests and supports various types of API requests, environments, and authentication methods.
  • SoapUI: While it initially focused on SOAP services, SoapUI now supports RESTful APIs as well. It’s suitable for functional testing and also offers security and performance testing features. SoapUI allows for test automation and can be integrated into CI/CD pipelines.
  • Katalon Studio: A comprehensive tool that supports both API and UI testing. Katalon Studio is designed for testers of all levels and integrates with other tools and frameworks like JIRA, qTest, Kobiton, and Jenkins for CI/CD pipelines.
  • Jest is a delightful JavaScript Testing Framework with a focus on simplicity, supporting tasks like unit testing, and when combined with Supertest, it becomes a powerful tool for testing HTTP APIs.
  • Rest Assured is a Java DSL for simplifying testing of REST-based services. It supports given/when/then test notation, which is very readable and aligns with modern testing practices.

Performance Testing

  • JMeter: An open-source tool designed for load testing and measuring performance. Originally developed for web applications, JMeter can also test RESTful and SOAP APIs. It supports a wide variety of tests, including load, stress, and spike testing.
  • Gatling: Gatling is another open-source tool focused on load testing. It’s known for its high performance and is designed for ease of use. Gatling provides detailed performance reports and supports integration with CI/CD tools.
  • LoadRunner: A comprehensive load testing tool from Micro Focus that supports a wide range of technologies beyond APIs, including web and mobile apps. LoadRunner simulates thousands of users to test the performance of APIs under heavy load and provides detailed analytics.
  • Locust; A python open source tool, for which I created a small cdk setup at scale

Security Testing

  • OWASP ZAP (Zed Attack Proxy): An open-source security tool specifically designed for testing web application security, which can be extended to test APIs for vulnerabilities like SQL Injection, Cross-Site Scripting (XSS), and more.
  • Burp Suite: A popular tool for web application security testing, also effective for testing the security of web APIs. It offers automated scanning as well as tools for manual penetration testers to identify and exploit security vulnerabilities.
  • Postman: Besides functional testing, Postman can be used for basic security testing, such as checking for proper authentication, encryption, and SQL injection vulnerabilities by crafting malicious API requests.
  • API Security.io: A cloud-based API security scanner, which scans REST and GraphQL APIs for security issues. It checks for common security vulnerabilities and misconfigurations.

Combination of tools

Some tools provide functionality for more than one type of testing:

  • SoapUI: Offers functionality for functional, performance, and basic security testing of APIs.
  • Katalon Studio: Supports functional, performance (via integration with JMeter), and basic security testing.

Conclusion

Creating a great API involves careful planning, a focus on security and performance, and a commitment to usability and clear communication. By following the guidelines outlined in this document, you can design an API that is not only powerful and efficient but also easy for developers to use and integrate into their application

by Ralph Van Der Horst

arrow right
back to blog

share this article

Relevant articles

 Allure Reporting in webdriverio extending the code

Feb 25, 2024

Allure Reporting in webdriverio extending the code

Property Based Testing

Feb 25, 2024

Property Based Testing

API/XPATH/CSS cheatsheet in google sheets

Apr 2, 2024

API/XPATH/CSS cheatsheet in google sheets