'403 forbidden error when accessing API with X-API-KEY authentication

I stumbled upon an error when trying to access “EDQM Standard Terms” dictionary via API — this is a dictionary of various pharmaceutical formulations used in the EU, and I wanted to use a Python script to fetch the data for further processing. The API documentation published by EDQM is really scarce: Standard Terms API documentation

I tried accessing one of the endpoints listed in API documentation — namely, a list of languages — and no matter what I try, I get 403 FORBIDDEN error as a reply. I checked this directly from Python, as well as with Postman, and the result is the same. I double-checked and I am using the correct API key, and the Web Service is active for my EDQM account.

I wrote the following functions:

  1. assemble_string
def assemble_sig(verb: str, uri: str, timestamp: str):
    string_to_sign = verb + "&" + uri + "&" + HOST + "&" + timestamp
    return string_to_sign

This function generates the signature mentioned in API docs based on the URI, ‘GET’ verb and current time/date.

  1. sign_string
def sign_string(key: str, msg: str):
    signature = hmac.new(
        key.encode(),
        msg.encode(),
        digestmod=hashlib.sha512,
    ).hexdigest()
    signature_b64 = base64.b64encode(signature.encode())
    signature_final = signature_b64.decode()
    return signature_final[-22:]

This function takes my API key and the assembled string, encodes it and returns the 22 last characters.

  1. api_header
def api_header(uri: str):
    d = dt.datetime.now()
    edqm_timestamp = format_date_time(d.timestamp())
    
    api_signature = assemble_sig(HTTP_VERB, uri, edqm_timestamp)
    signed_string = sign_string(API_KEY, api_signature)

    api_hdr = {
        "Date": edqm_timestamp,
        "X-STAPI-KEY": f"{LOGIN}|{signed_string}",
    }
    return api_hdr

This function, using the previous two ones, assembles the header for the API request. Generated header looks like this:

{'Date': 'Sun, 06 Feb 2022 16:43:06 GMT', 'X-STAPI-KEY': 'mylogin|FhYzRiMTllNjhlNzQ0MWI='}

where mylogin is replaced with my real login.

The entire code for the API request:

edqm_req_url = f"{HOST}{URI['languages']}"
edqm_req_header = api_header(URI["languages"])

edqm_session = requests.Session()
edqm_req = edqm_session.get(
    edqm_req_url,
    headers=edqm_req_header,
)
print(edqm_req.raise_for_status())

I already tried the following:

  • different encoding in sign_string function — when encode() or decode() are used with UTF-8 or ASCII encoding parameter, I get 400 BAD REQUEST;
  • using Authorization header {"Authorization": "X-STAPI-KEY = mylogin|signed_string”}, but I also got 400 BAD REQUEST.

I will really appreciate any help with getting this thing working!

Thanks :-)

Kuba



Solution 1:[1]

I've successfully accessed the edqm api with authentication. Let's describe the variable of the signature.

- StringToSign = HTTP-Verb + "&" + URI + "&" + HOST + "&" + Date
  - HTTP-Verb
    - http request method
    - usually like GET?POST
  - URI
    - http request uri
    - exclude scheme?host from url
    - for example if the url is **https://standardterms.edqm.eu:443/standardterms/api/v1/domains**, the true uri is **/standardterms/api/v1/domains**
  - HOST
    - http request host
    - constant, should be **standardterms.edqm.eu**
  - Date
    - same with the Date header as above
  - &
    - just a string value not operator
- 22 last characters of Base64( HMAC-SHA512( YourSecretAccessKey, StringToSign ) )
  - you need to sign **StringToSign** with **YourSecretAccessKey** using HMAC-SHA512
  - then encoded with Base64
  - finally keep the last 22 characters of the base64 result

You could access my github https://github.com/asd1245dss/edqm-sync/edit/main/README.md to learn more

Solution 2:[2]

I also managed to get the API working, although I had to contact their IT support. Below I am posting the working solution:

String to sign should be as specified in the help page, as shown below: StringToSign = HTTP-Verb (=GET) + "&" + URI (=/standardterms/api/v1/languages) + "&" + HOST (=standardterms.edqm.eu) + "&" + Date; For this request, try to use: 'GET&/standardterms/api/v1/languages&standardterms.edqm.eu&Mon, 07 Feb 2022 12:35:02 GMT'

If using this, you still do not get the expected result, you could check your signature generation script. Here are the testing parameter:

String to sign: "GET&/standardterms/api/v1/languages&standardterms.edqm.eu&Mon, 07 Feb 2022 14:20:00 GMT" Secret key: "mysecret" Generated signature: "Z3DZ5tAtcmHGjeA4MUQw=="

It should also be noted that the HOST parameter should not include 'http://' or 'https://'.

Cheers, Kuba

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 SummerXY
Solution 2 jkosciel