APIs have become the backbone of modern applications, allowing seamless integration between systems. However, with great connectivity comes great responsibility. Securing APIs And Enhancing Flexibility and Security is critical, especially when handling sensitive or high-value data. In this blog post, we’ll dive into how we enhanced the security and flexibility of our previously developed API by introducing dynamic API keys.
Why API Keys?
API keys are one of the simplest and most effective ways to authenticate and authorize API requests. By assigning a unique API key to each client, you can:
- Control access to your API.
- Monitor usage and track clients.
- Reduce unauthorized access or misuse.
In our case, we wanted to ensure that only authorized servers or processes could submit data to our API while still allowing flexibility in adding new clients dynamically.
The Challenge
Our existing API allowed servers to submit custom metrics and store them in an InfluxDB database. However, the API lacked any form of authentication, leaving it vulnerable to unauthorized use. Moreover, we needed a way to associate specific API keys with their corresponding clients (servers or hostnames).
To address this, we:
- Added API key validation to the
/api/submit
route. - Created a SQLite database to store API keys and associate them with specific hostnames. later we can build a Web-Based SQLite Database Manager to manage our database.
- Implemented a helper script to dynamically generate and store API keys.
Enhancements to the API
Storing API Keys
We introduced a SQLite database to store API keys and their corresponding hostnames. Here’s how we set it up:
Copied!sqlite3 api_keys.db <<EOF CREATE TABLE IF NOT EXISTS api_keys ( id INTEGER PRIMARY KEY AUTOINCREMENT, api_key TEXT NOT NULL UNIQUE, hostname TEXT NOT NULL ); EOF
Generating API Keys
To simplify API key management, we wrote a Python script to generate and store API keys:
Copied!import sqlite3 import uuid DB_FILE = 'api_keys.db' def generate_api_key(hostname): api_key = str(uuid.uuid4()) conn = sqlite3.connect(DB_FILE) cursor = conn.cursor() cursor.execute('INSERT INTO api_keys (api_key, hostname) VALUES (?, ?)', (api_key, hostname)) conn.commit() conn.close() return api_key if __name__ == "__main__": hostname = input("Enter hostname: ") api_key = generate_api_key(hostname) print(f"API Key for {hostname}: {api_key}")
Validating API Keys
We updated the API to validate the API key against the database and ensure it matches the client’s hostname:
Copied!def is_valid_api_key(api_key, hostname): conn = sqlite3.connect(DB_FILE) cursor = conn.cursor() cursor.execute('SELECT hostname FROM api_keys WHERE api_key = ?', (api_key,)) result = cursor.fetchone() conn.close() return result and result[0] == hostname
The /api/submit
route was updated to check the validity of the API key before processing the request.
Copied!# Hostname/IP hostname = request.remote_addr # Check if the API-key is valid if not is_valid_api_key(api_key, hostname): logger.warning("Invalid API key or hostname mismatch") return jsonify({"error": "Unauthorized"}), 401
Updated API Workflow
Here’s the updated workflow:
- A server sends a POST request to
/api/submit
, including its API key in theX-API-Key
header. - The API extracts the API key and hostname from the request.
- It validates the API key against the database.
- If valid, the API processes the request and writes the data to InfluxDB.
Example request:
Copied!curl -X POST http://127.0.0.1:5002/api/submit \ -H "Content-Type: application/json" \ -H "X-API-Key: YOUR_API_KEY_HERE" \ -d '{"field": "os_updates", "value": 5, "timestamp": 1699786600000000000}'
Benefits of the API Key Implementation
- Enhanced Security: Only authorized clients can access the API.
- Scalability: New clients can be added dynamically without modifying the API code.
- Accountability: Misuse can be traced back to the offending API key.
- Customizable Access: You can assign specific privileges or limits to each API key.
Looking Ahead
With this implementation, we’ve taken a significant step toward a secure and robust API. However, there’s always room for improvement. Here are some potential future enhancements:
- Rate Limiting: Prevent abuse by limiting the number of requests per API key.
- Role-Based Access Control (RBAC): Assign specific permissions to API keys.
- Token Expiration: Introduce time-limited API keys for temporary access.
By integrating API key authentication into our API, we’ve laid the foundation for a secure and scalable system. Whether you’re tracking server updates or monitoring sales metrics, this approach ensures that only trusted clients can interact with your API, giving you peace of mind and operational control.