Building a Simple and Secure API with Python Flask

Server-Side: Flask API Development

We will build a simple API that will tell the current time. And then add more feature.

The explanation and the code

The code



from flask import Flask, request, jsonify
from datetime import datetime

app = Flask(__name__) 

# Define a route to get the current time
@app.route('/get-current-time', methods=['POST'])
def get_current_time():
    # Validate the API key
    api_key = request.headers.get('Authorization')
    if api_key != '12345': 
        return jsonify({"error": "Unauthorized"}), 401 

    # Get and format the current time
    current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")

    # Return the time in JSON format
    return jsonify({"currentTime": current_time}), 200 

if __name__ == '__main__':
    app.run(port=5000, debug=True) 





Route Definition: Use the @app.route() decorator to define the URL path (/get-current-time) for your API endpoint. This specific endpoint is configured to handle POST requests.
Endpoint Logic: Inside the get_current_time() function (which handles requests to your endpoint), retrieve the API key from the incoming request's headers
Authorization Check: Implement a security measure by verifying that the provided API key matches the correct value. If the key is invalid, return a JSON error response with a 401 (Unauthorized) HTTP status code.

Response Preparation: Construct a JSON dictionary (response_data) to hold the current time value.

Client-Side: Calling the API




import requests

url = 'http://localhost:5000/get-current-time'
api_key = '12345'

headers = {
  'Authorization': api_key,
  'Content-Type': 'application/json',
}

try:
  response = requests.post(url, headers=headers)

  if response.status_code == 200:
    response_data = response.json()
    current_time = response_data.get('currentTime')
    print("Current time received from the API:", current_time)
  else:
    print("Error:", response.status_code, response.text)  # Include response text
except requests.exceptions.RequestException as e:
  print("Error On request:", e)





In the client side we will use requests module to retrieve the time with the API request.

requests module is not a built-in module in Python. So if you have not installed you need to install it first.

pip install requests

The code and the explanations

url = 'http://localhost:5000/get-current-time'
api_key = 'oCZ2DNROIRSyL058'

headers = {
  'Authorization': api_key,
  'Content-Type': 'application/json',
}

Set API URL and Key: Configure the address of your API endpoint and your valid API key.
Create Headers: Build a dictionary containing your API key for authorization and the content type (application/json).


Testing the code

  • First we need to run the code we have add in the top for server side. It will run a server on local host http://localhost:5000/
  • Then we need to run another python script that is written for the client side. That’s all! It will print the time which comes from the server.

This was just for essential approach. We can make it more practical. For example we need check if a number is prime or not but we do not want to write the core functionality in our client side code. So we can write the API for it .. And call when ever we need and wherever we are!

We will use Miller-Rabin Primality Test for more efficiency to check a number is prime or not.

The code that checks the number is prime or not




import random

def is_prime_miller_rabin(n, iterations=40):
    """Performs the Miller-Rabin Primality Test to determine if a number is likely prime.

    This test is probabilistic, meaning it's highly accurate but not foolproof.
    Increasing the 'iterations' value improves the accuracy of the result.

    Args:
        n: The integer to test for primality.
        iterations: Number of test rounds to perform (default is 40).

    Returns:
        True if the number is likely prime, False if it's not.
    """

    if n <= 1:
        return False
    if n <= 3:
        return True
    if n % 2 == 0:
        return False

    # Decompose n-1 into the form 2^r * d
    d = n - 1
    r = 0
    while d % 2 == 0:
        d //= 2
        r += 1

    # Witness loop
    for _ in range(iterations):
        a = random.randint(2, n - 2)
        x = pow(a, d, n)
        if x == 1 or x == n - 1:
            continue
        for _ in range(r - 1):
            x = pow(x, 2, n)
            if x == n - 1:
                break
        else:
            return False
    return True



# Test the code
number_to_test = int(input("Enter a number to check for primality: "))
if is_prime_miller_rabin(number_to_test):
    print(f"{number_to_test} is likely a prime number.")
else:
    print(f"{number_to_test} is not a prime number.") 





If you are interested in prime numbers you also can read this article

Implemented In The server code( API)




from flask import Flask, request, jsonify
from datetime import datetime
import random

app = Flask(__name__) 

def is_prime_miller_rabin(n, iterations=40):
    """Performs the Miller-Rabin Primality Test to determine if a number is likely prime."""
    if n <= 1:
        return False
    if n <= 3:
        return True
    if n % 2 == 0:
        return False

    # Decompose n-1 into the form 2^r * d
    d = n - 1
    r = 0
    while d % 2 == 0:
        d //= 2
        r += 1

    # Witness loop
    for _ in range(iterations):
        a = random.randint(2, n - 2)
        x = pow(a, d, n)
        if x == 1 or x == n - 1:
            continue
        for _ in range(r - 1):
            x = pow(x, 2, n)
            if x == n - 1:
                break
        else:
            return False
    return True

# Define a route to check if a number is prime
@app.route('/check-prime', methods=['POST'])
def check_prime():
    # Validate the API key
    api_key = request.headers.get('Authorization')
    if api_key != '12345': 
        return jsonify({"error": "Unauthorized"}), 401 

    # Get the number from the request
    data = request.get_json()
    number_to_test = data.get('number')

    # Check if the number is prime
    is_prime = is_prime_miller_rabin(number_to_test)

    # Return the result
    return jsonify({"number": number_to_test, "isPrime": is_prime}), 200 

if __name__ == '__main__':
    app.run(port=5000, debug=True) 






Calling from the client side




import requests

url = 'http://127.0.0.1:5000/check-prime'
headers = {'Authorization': '12345'}
data = {'number': 172983479832623897492387592384682937659382477}

response = requests.post(url, json=data, headers=headers)
print(response.json())






Limit the API calling: Setting the Rate-Limiting

We can specifically set the daily limit or hourly limit for specific user to save the computation limits. For this we will use a wonderful library apscheduler . I first used this library on video calling project for user data management scheduler.

Install the library with

pip install apscheduler

Now our code will look like this




from flask import Flask, request, jsonify
from datetime import datetime, timedelta
import random
from apscheduler.schedulers.background import BackgroundScheduler

app = Flask(__name__)

def is_prime_miller_rabin(n, iterations=40):
    """Performs the Miller-Rabin Primality Test to determine if a number is likely prime."""
    if n <= 1:
        return False
    if n <= 3:
        return True
    if n % 2 == 0:
        return False

    # Decompose n-1 into the form 2^r * d
    d = n - 1
    r = 0
    while d % 2 == 0:
        d //= 2
        r += 1

    # Witness loop
    for _ in range(iterations):
        a = random.randint(2, n - 2)
        x = pow(a, d, n)
        if x == 1 or x == n - 1:
            continue
        for _ in range(r - 1):
            x = pow(x, 2, n)
            if x == n - 1:
                break
        else:
            return False
    return True

# Initialize the scheduler
scheduler = BackgroundScheduler()

# Define the users and their limits
users = {"12345": {"limit": 5}, "45365": {"limit": 5}}

# Function to reset the limits
def reset_limits():
    print("Resetting limits...")
    for user_info in users.values():
        user_info["limit"] = 5

# Schedule the job to run every 24 hours
scheduler.add_job(reset_limits, 'interval', hours=24)

# Start the scheduler
scheduler.start()

# Define a route to check if a number is prime
@app.route('/check-prime', methods=['POST'])
def check_prime():
    # Validate the API key
    api_key = request.headers.get('Authorization')
    if api_key not in users:
        return jsonify({"error": "Unauthorized"}), 401

    # Get the number from the request
    limit = users[api_key]['limit']
    if not limit == 0:
        data = request.get_json()
        number_to_test = data.get('number')

        # Check if the number is prime
        is_prime = is_prime_miller_rabin(number_to_test)

        # Update the request limit
        users[api_key]['limit'] -= 1

        # Return the result
        return jsonify({"number": number_to_test, "isPrime": is_prime}), 200
    else:
        return jsonify({"error": "Request limit exceeded. Please try again later."}), 429

if __name__ == '__main__':
    app.run(port=5000, debug=True)






Client Side will be unchanged!




import requests

url = 'http://127.0.0.1:5000/check-prime'
headers = {'Authorization': '12345'}
data = {'number': 172983479832623897492387592384682937659382477}

response = requests.post(url, json=data, headers=headers)
print(response.json())





Enhance Security Level

For demonstration purposes, this example stores the API key directly in the code. In production environments, always store API keys securely in a database with hashing to protect sensitive information.

With timestamp make the API key ultimately unique when generate new key for user




import secrets
import string
import time

def generate_unique_api_key(length=32):
    """Generate a unique API key using a combination of random characters and timestamp."""
    timestamp = str(int(time.time()))  # Get current timestamp as a string
    alphabet = string.ascii_letters + string.digits
    random_part = ''.join(secrets.choice(alphabet) for _ in range(length - len(timestamp)))
    key = timestamp + random_part
    return key

# Generate a unique API key of length 32
api_key = generate_unique_api_key()
print("unique API key:", api_key)






For You

For more easy to use, I added the code in the github repository