-
-
Notifications
You must be signed in to change notification settings - Fork 528
HMAC 512 auth #1434
-
How can I integrate with swagger hence openapi if my api has HMAC 512 auth. So clients have api key and secret key. So when clients want to make request they must provide api key, timestamp and signature as http headers. Also i would like provide ability to the clients to enter into swagger api key and secret key as vars so that swagger automatically calculate signature on request and add needed headers to authorize request.
Beta Was this translation helpful? Give feedback.
All reactions
Replies: 3 comments
-
Hi @anryangelov
I'm afraid swagger does not support that out of the box
as of python implementation - if you can provide example python code of your inputs, validation and finding user/principal - I can give you example on how to integrate that with django ninja
Beta Was this translation helpful? Give feedback.
All reactions
-
Hi @vitalik thanks for the answer. Below is the exact description (click to the arrow) which I provided to description parameter when initialize the api instance. I hope this help for clarifying. So I do not how to do this interactive in swagger so that i can input api key and api secret as vars. Surely with some javascript but I am just wondering which is most straightforward and elegant way for doing this. Also is there a better place where I can explain how auth happened in openapi. For now I use redoc because just looks better. By the way can I use both swagger and redoc at same time with one api instance as well at least for development? . Also I forgot to mention that initially inherit from ninja security api class but currently i move auth logic in django middleware if that meters.
Authentication
This API uses HMAC authentication.
Authentication instructions
Required Headers:
- X-API-key: Your API key.
- X-API-Timestamp: Current timestamp in milliseconds.
- X-API-Signature: HMAC-SHA512 signature computed with your API secret and message.
Message Format:
The signature is computed over the concatenation of the following components:
- HTTP method (uppercase)
- Request path (with query parameters if applicable)
- Timestamp (milliseconds, also used as nonce)
- Request body (if present)
For example, a POST
request to {{external_url_prefix }}foo
with a body of {"bar": "value"}
and a timestamp
of 1610000000000
would use the message:
POST{{ external_url_prefix }}foo1610000000000{"bar": "value"}
Bash Example
#!/bin/bash API_KEY="your_api_key" API_SECRET="your_api_secret" TIMESTAMP=$(date +%s)000 # or `date +%s%3N` PATH="{{ external_url_prefix }}ping" BODY="" MESSAGE="${METHOD}${PATH}${BODY}${TIMESTAMP}" SIGNATURE=$(echo -n "$MESSAGE" | openssl dgst -sha512 -hmac "$API_SECRET" | sed 's/^.* //') curl -X GET "{{ host90k }}{{ external_url_prefix }}ping" \ -H "X-API-key: $API_KEY" \ -H "X-API-Timestamp: $TIMESTAMP" \ -H "X-API-Signature: $SIGNATURE" \ -d "$BODY"
Python Example
import time, hmac, hashlib, requests API_KEY = 'your_api_key' API_SECRET = 'your_api_secret' METHOD = 'GET' PATH = '{{ external_url_prefix }}ping' BODY = '' timestamp = str(int(time.time_ns() / 1000_000)) message = METHOD + PATH + BODY + timestamp signature = hmac.new(API_SECRET.encode('utf-8'), message.encode('utf-8'), hashlib.sha512).hexdigest() response = requests.get( '{{ host90k }}' + PATH, headers={ 'X-API-key': API_KEY, 'X-API-Timestamp': timestamp, 'X-API-Signature': signature }, data=BODY ) print(response.status_code, response.json())
Always use HTTPS to ensure secure transmission.
Beta Was this translation helpful? Give feedback.
All reactions
-
here is what me and LLM come with:
api:
from ninja import NinjaAPI from ninja.security import APIKeyHeader import hmac import hashlib API_SECRET = 'your_api_secret' class HMACAuth(APIKeyHeader): param_name = 'X-API-key' def authenticate(self, request, token): request_signature = request.headers['X-API-Signature'] request_timestamp = request.headers['X-API-Timestamp'] message = request.method + request.build_absolute_uri() + request.body.decode() + request_timestamp expected_signature = hmac.new(API_SECRET.encode('utf-8'), message.encode('utf-8'), hashlib.sha512).hexdigest() print(f'message: {message}') print(f"Expected Signature: {expected_signature}") print(f"Request Signature : {request_signature}") if expected_signature == request_signature: return token return None api = NinjaAPI(auth=HMACAuth()) @api.get("/hello") def hello(request): return {"message": "Hello World", "valid_token": request.auth}
here is swagger part (Note: you need to add "ninja" to INSTALLED_APPS and overwrite templates/ninja/swagger.html
)
templates/ninja/swagger.html
{% load static %} <!DOCTYPE html> <html> <head> <link type="text/css" rel="stylesheet" href="{% static 'ninja/swagger-ui.css' %}"> <link rel="shortcut icon" href="{% static 'ninja/favicon.png' %}"> <title>{{ api.title }} with HMAC</title> </head> <body data-csrf-token="{% if add_csrf %}{{ csrf_token }}{% endif %}" data-api-csrf="{% if add_csrf %}true{% endif %}"> <script type="application/json" id="swagger-settings"> {{ swagger_settings | safe }} </script> <div id="swagger-ui"></div> <script src="{% static 'ninja/swagger-ui-bundle.js' %}"></script> <!-- Below is the custom script that will calculate HMAC signatures and attach them to requests --> <script> function bufferToHex(buffer) { return Array.from(new Uint8Array(buffer)) .map(b => b.toString(16).padStart(2, "0")) .join(""); } // Asynchronous function to compute HMAC SHA-512 async function signMessage(secret, data) { const encoder = new TextEncoder(); const key = await crypto.subtle.importKey( "raw", encoder.encode(secret), { name: "HMAC", hash: "SHA-512" }, false, ["sign"] ); const signature = await crypto.subtle.sign("HMAC", key, encoder.encode(data)); return bufferToHex(signature); } const csrfSettings = document.querySelector("body").dataset const configJson = document.getElementById("swagger-settings").textContent; const configObject = JSON.parse(configJson); configObject.dom_id = "#swagger-ui"; configObject.presets = [ SwaggerUIBundle.presets.apis, SwaggerUIBundle.SwaggerUIStandalonePreset ]; configObject.requestInterceptor = async (req) => { // req.headers['X-CSRFToken'] = csrfSettings.csrfToken const SECRET = "your_api_secret"; const API_KEY = "some_api_key"; // or what you expect clients to send as X-API-key // Timestamp: you might use epoch seconds or a full millisecond timestamp const currentTimestamp = Date.now().toString(); req.headers["X-API-Timestamp"] = currentTimestamp; // Body might be undefined, so handle carefully: const requestBody = req.body ? JSON.stringify(req.body) : ""; // Construct the message used in your HMAC formula // The path is in req.url, and the method is in req.method: const message = req.method + req.url + requestBody + currentTimestamp; console.log("Message to sign:", message); // Compute signature, attach to request const signature = await signMessage(SECRET, message); req.headers["X-API-Signature"] = signature; // Also provide the "param_name" used for your auth (X-API-key): req.headers["X-API-key"] = API_KEY; return req; }; const ui = SwaggerUIBundle(configObject); </script> </body> </html>
currently all keys are static in html - but you can probabl pull it from swagger auth dialog - if you manage to do that please share your solution
Beta Was this translation helpful? Give feedback.