← Back to blog
API Security Best Practices: Tips, Tricks & Code Examples

API Security Best Practices: Tips, Tricks & Code Examples

authors photo
Written by Dante Lex
Wednesday, May 18th 2022

You can’t get around APIs: API requests represent 54% of all web traffic, and developers spend 30% of their working hours coding APIs. In fact, APIs are so prevalent that 91% of surveyed organizations experienced an API security incident in 2020―implementing API security best practices is not just a nice-to-have, it’s mandatory for your business to thrive.

What’s API Security

API security is about protecting APIs from malicious usage.

Let’s say you have an API to create and read social profiles. You need to enable your users to change their own profiles, but you don’t want anyone else to be able to use the corresponding API or overload your web server with mass spam requests. API security best practices not only prevent users from corrupting their own data but also protect them from attackers.

Why API Security

A study by Salt Security showed that 34% of companies do not have an API security strategy, even with APIs running in a production environment. It’s highly problematic because APIs can be accessed by anyone with an Internet connection, and an unsafe API is basically a free ticket to your databases.

A data breach costs $4.23 million on average according to IBM Security because you’ll not only lose your customer’s trust and contracts but also spend a lot of resources fixing your codebase and issuing legal statements. Ignoring API security has the potential to bring a bundle of sunk costs you’d be better off without!

Lastly, bad security is terrible marketing. When people trust you with their data, they expect great care on your end. Building a respectable brand implies putting your users’ safety first, and that takes planning and commitment but you cannot overlook it.

8 API Security Best Practices

1. Input Validation

API security starts during development. Each software function receives parameters to produce a result, but you can’t trust these parameters blindly when they are sent by strangers on the Internet. You need to validate user input to prevent SQL injections and XSS attacks:

  • Sanitize everything that validates data types, test data encoding, and formatting, and escape sensitive characters like quotes or backslashes that can trigger parsing errors
  • Pick a database ORM to handle safe database access - if you don’t know what you’re doing―not everybody is a database expert―it’s best to just abstract your data access layer in a trusted open-source library.

A Javascript example for an API that requests users by email address using Express as a web server and Sequelize as a database ORM:

import express from 'express'
import * as EmailValidator from 'email-validator'

// we use Sequelize as an ORM
import { Model } from 'sequelize'
class User extends Model {}

const router = express.Router()

//get a user by email
router.get('/api/user', async (req, res) => {
		const email = req.query.email
 
	  //if the email is incorrect, send a Bad Request HTTP error
    if(!EmailValidator.validate(email)){
			return res.status(400).json({ ok: false })
		}

		const user = await User.findOne({
			where: {
				email
			}
		})

    return res.status(200).json({ ok: true, user })
})

export default router

2. API Authentication & Authorization

Impersonating a user is one of the first things that comes to mind to damage a digital product. A web API is just a bag of public HTTP endpoints that can be accessed from anywhere in the world, so brute-forcing your way to a web server through unauthorized access to an API looks like a no-brainer. This is why you need a strong authentication and authorization process.

First, how do we know who is who? There are 3 main API authentication best practices:

  • API keys - API keys are simple to use and set up, they are basically passwords. They are best when you want to allow a user to interact with your product from outside its user interface.
  • OAuth tokens -OAuth is a bit more complex as it relies on two sorts of tokens to work―a temporary access token to perform requests, and a refresh token to periodically ask for new access tokens―but this complexity brings additional security features.
  • JSON Web Tokens (JWT) - JWT can store additional non-sensitive information about the user without additional requests at the price of a bigger payload. They can’t be revoked from the server, so a JWT needs an expiration date by design. A JWT is great for in-app calls to your web server when combined with session cookies.

Each method has pros & cons, but if you’re only getting started with API auth we advise you to start with a combination of API keys and JWTs.

Then you need to define who can do what―authorization. An admin user can do more things than a regular user, for example. For that, you can define roles in your application and attach these roles to your preferred authentication method. For an API key, you could store roles as a serialized array in your database entry. With a JWT, you can define roles directly in the payload of each JSON token.

Whether you need to call external APIs within your API or need to encrypt your authentication tokens, you can use Onboardbase to securely store your API keys.

3. API Encryption

API encryption prevents attackers from analyzing data in transit―man-in-the-middle attacks. Two elements to implement in your API system:

While HTTPS is a default setting for most web apps nowadays, 6% of the world’s largest websites still don’t use secure connections. HTTP with TLS encryption encrypts/decrypts requests and responses to/from web servers, so lacking HTTPS leaves you open to data breaches on insecure networks where attackers can monitor your traffic and learn sensitive information or impersonate users with your HTTP requests.

Perfect forward secrecy (PFS) uses elliptic curve cryptography (ECC) to create temporary encryption keys on each HTTP transaction to enable secure API calls. The Heartbleed attack showed that HTTPS doesn’t guarantee the invulnerability of your data, so adding an extra layer of security on top of PFS isn’t overkill if you want to take security seriously.

You can use your temporary ECC keys as private keys for symmetric encryption algorithms like AES-256 to encrypt your data payload on each API request, making it impossible for attackers to decrypt your API traffic.

4. IP Whitelisting & CORS Proxy

A common practice for API providers is to prevent all IP addresses and domains from accessing the service, except for a few select ones. Disallowing unverified IPs and domains from using your API greatly decreases the potential attack surface of malicious users, and you’ll be able to quickly figure out who to target if something goes wrong. You can do this at two levels: with a reverse proxy or a regular web server using IP whitelisting, or at the transport, level using CORS policies in HTTP headers.

IP whitelisting example using Caddy server:

private.api.com {
  @blocked not remote_ip 1.2.3.4 # block all incoming IPs except the address 1.2.3.4
  respond @blocked "<h1>Access Denied</h1>" 403
  
  # proceed with other requests if the IP is authorized:
	...
}

CORS example with a NodeJS server using the Express library:

var express = require('express')
var cors = require('cors')
var app = express()

var corsOptions = {
  origin: 'https://ourapi.com' // only allow HTTP requests incoming from this origin 
}

app.use(cors(corsOptions))

You will need to whitelist your localhost and use tunneling software during staging or production to test your API

5. API Rate Limiting

API protection by rate-limiting is also a best practice to prevent spam behaviors: if you can’t call an API method more than once per minute per user, you’ll drastically minimize the number of HTTP requests. Rate limiting is a simple solution to prevent denial-of-service (DoS) attacks that are getting increasingly frequent while managing server costs.

You can find configurations to implement rate-limiting in the Caddy server configuration. Usually, rate limiting is best performed at the reverse proxy level rather than within your web server program, because reverse proxies like Nginx or Caddy are much faster at handling requests than web servers written in NodeJS or Rails for example.

6. API Load Balancing

Another way to prevent DoS attacks is to use load balancers, content delivery networks, and floating IPs.

A load balancer is a reverse proxy splitting incoming traffic among multiple local web servers to prevent bottlenecks. You can do this using Caddy intuitively:

reverse_proxy server1:80 server2:80 server3:80

Cloudflare’s content delivery network also offers load balancing of static assets like cached API responses across a wide geographical area. CDNs and load balancers are two different things though: a CDN sits in front of your cluster of servers managed by a load balancer. If a server goes down, the load balancer will re-route the data to another server while a CDN will simply stop serving requests.

7. API Testing

Testing is another common best practice to make sure our API behaves as expected. Unlike pieces of software with more complex features, APIs are pretty straightforward to test as they always follow a fixed contract:

  • Unit testing - testing individual functions
  • Functional testing - testing API endpoints, performance, and auth
  • Load testing - testing how much traffic load our API infrastructure can handle

Unit testing is typically used to test your CRUD services. In Javascript for example you can use the mocha library to automate your unit tests:

import assert from 'assert';

describe('UserService', () => {

  describe('#create()', () => {
    it('should create a new User instance', () => {
		    const email = 'ok@gmail.com'
        const user = {
		      email
        }
        assert.equal(user.email, email);
    })
  })

})

Functional testing can be implemented to make sure your API respects security best practices like authentication:

import assert from 'assert';

describe('#getByEmail()', () => {
  it('should query a User by email', async () => {
	    const res = await fetch('http://localhost:5000/api/user?email=test@gmail.com', {
				headers: {
					'Authorization': `Bearer ${a wrong API token}`
				}
			})
      assert.equal(res.status, 401); // should send an Unauthorized 401 error code
  })
})

Load testing is a subset of functional testing that focuses on the resilience of your API against crashes and traffic spikes. An example with the k6 package:

import http from 'k6/http';

import { sleep } from 'k6';

export default function () {

  http.get('http://localhost:5000/api/user');

  sleep(1);

}

This test will call our API endpoint. You can then run the following command to simulate 10 users requesting your API endpoint over a duration of 30 seconds:

k6 run --vus 10 --duration 30s script.js

You can find 2 million repositories related to API development on Github, which are quite interesting to read to learn about their testing practices or lack thereof.

8. API Monitoring Tools

API testing checks if your API works at a given point in time, but once your API hits its production environment, you want to make sure your API works at all times using monitoring tools. Monitoring tools track API responses and performance in real-time to find odd patterns that could lead to a security breach. Two possibilities:

  • Use an HTTP monitoring service like Instatus that will ping your API on a regular basis
  • Use your web server’s logging feature and feed it to a log monitoring tool like Prometheus or Elasticsearch

HTTP monitoring consists in setting up CRON jobs on a web server that will send HTTP requests and test the responses. You can do it yourself or use services like Instatus that will abstract everything away for you.

Log monitoring leverages your web server’s logging feature to analyze traffic and find errors. Most web servers can log their HTTP traffic to a log file if you enable it in their settings. Caddy’s logs do it right away without configuration needed.

Use Onboardbase For API Security

Securing an API includes securing your API keys and credentials to access said APIs as environment variables. But sharing environment variables using .env files or instant messaging platforms is rarely secure: you need tools like Onboardbase to store your app secrets in a secure vault your team can access while injecting them as environment variables in your programs at runtime.

Onboardbase is an environment variable manager made for developer teams that facilitates defining, sharing, and integrating environment variables in any development workflow. A secure dashboard centralizes app configurations for the whole team, and a command-line interface brings your environment variables in all your software projects. You can get started for free in a minute without disrupting your current workflow, it’s that simple!

Subscribe to our newsletter

The latest news, articles, features and resources of Onboardbase, sent to your inbox weekly