Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

HMAC 512 auth #1434

Unanswered
anryangelov asked this question in Q&A
Discussion options

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.

You must be logged in to vote

Replies: 3 comments

Comment options

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

You must be logged in to vote
0 replies
Comment options

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:

  1. HTTP method (uppercase)
  2. Request path (with query parameters if applicable)
  3. Timestamp (milliseconds, also used as nonce)
  4. 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.

You must be logged in to vote
0 replies
Comment options

@anryangelov

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>
SCR-20250403-tbvg

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

You must be logged in to vote
0 replies
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Category
Q&A
Labels
None yet

AltStyle によって変換されたページ (->オリジナル) /