# Client Authentication for the NT API System

This document describes the authentication process in a language-agnostic way so it can be implemented in any programming language (e.g., Java, JavaScript/TypeScript, Python, PHP, Go).

## Overview

A signature header is generated for every request.

Required credentials:

- username
- accessKey
- secret
- baseUrl (e.g., https://publicapi.stage.nt.cloud.bewotec.de)

Authentication uses the following header:

Authorization: DirectGrant <username> <accessKey> <utcDate> <signature>

## 1) Prepare the request

- Set the HTTP method (e.g., GET, POST)
- Set the path (e.g., /td/travel-infos/4711)
- Set the query (e.g., ?q=100 or empty)
- Optionally set a JSON body

Important:

- Accept header: application/json
- If a JSON body is sent, also set Content-Type: application/json

## 2) Body hash (only if payload should be signed)

If a request body is present and should be included in the signature:

1. Serialize the JSON body as UTF-8 bytes
2. Compute SHA-256 over these bytes
3. Encode the result as Base64
4. Set the header:
   - x-nt-content-sha256: true

If no body is included, bodyHash is an empty string.

## 3) Generate timestamp

- utcDate in UTC format: yyyyMMddHHmmss
- Example: 20210118093334

## 4) Build canonical string

The canonical string is composed exactly as follows:

assembleUpper = utcDate + UPPER(httpVerb) + UPPER(path + query) + bodyHash

Example without body:

20210118093334GET/TD/TRAVEL-INFOS/4711?Q=100

Notes:

- Method MUST be uppercase
- path + query MUST be uppercase
- bodyHash is appended unchanged (can be empty)

## 5) Compute signature

1. Convert assembleUpper to UTF-8 bytes
2. Convert secret to UTF-8 bytes (HMAC key)
3. Compute HMAC-SHA256 over assembleUpper using secret
4. Encode the result as Base64

The result is signature.

## 6) Set Authorization header

authorization = "DirectGrant " + username + " " + accessKey + " " + utcDate + " " + signature

Set header:

- Authorization: <authorization>

## 7) Send request

Send request to:

baseUrl + path + query

with the headers set above and an optional JSON body.

---

## Strict specification for client generators

Use the rules below as normative requirements for generated clients.

### MUST rules

- The client MUST use UTF-8 for all cryptographic inputs.
- The client MUST generate utcDate in UTC with format yyyyMMddHHmmss (14 digits).
- The client MUST build canonical exactly as:
   - canonical = utcDate + UPPER(httpMethod) + UPPER(path + query) + bodyHash
- The client MUST use an empty string for query if no query exists.
- The client MUST compute signature as:
   - Base64(HMACSHA256(key = UTF8(secret), data = UTF8(canonical)))
- The client MUST format Authorization exactly as:
   - DirectGrant <username> <accessKey> <utcDate> <signature>
- The client MUST NOT insert additional spaces in Authorization.

### Payload signing rules

- If includePayload is true and a body exists:
   - bodyHash MUST be Base64(SHA256(UTF8(bodyJsonBytesActuallySent)))
   - x-nt-content-sha256: true MUST be sent
- Otherwise:
   - bodyHash MUST be an empty string
   - x-nt-content-sha256 MUST NOT be sent

### Consistency rules

- The same logical path + query MUST be used for signing and sending.
- The body used for bodyHash MUST be byte-identical to the body actually sent.

---

## Language-agnostic pseudocode

1. utcDate = utcNow("yyyyMMddHHmmss")
2. if hasBody and includePayload:
   - bodyJson = serializeJson(body)
   - bodyHash = base64(sha256(utf8(bodyJson)))
   - addHeader("x-nt-content-sha256", "true")
3. else:
   - bodyHash = ""
4. canonical = utcDate + upper(httpMethod) + upper(path + query) + bodyHash
5. signature = base64(hmacSha256(key=utf8(secret), data=utf8(canonical)))
6. auth = "DirectGrant " + username + " " + accessKey + " " + utcDate + " " + signature
7. addHeader("Authorization", auth)
8. addHeader("Accept", "application/json")
9. if hasBody: addHeader("Content-Type", "application/json")
10. send request to baseUrl + path + query

---

## Reference test vectors (golden samples)

Use these vectors to validate newly generated clients.

### Vector A: GET without body

- username: demo_user
- accessKey: 8370921743021
- secret: 321094343143
- utcDate: 20210118093334
- method: GET
- path: /td/travel-infos/4711
- query: ?q=100
- bodyHash: (empty string)
- canonical:
   - 20210118093334GET/TD/TRAVEL-INFOS/4711?Q=100
- expected signature:
   - TTmZSmLxSexK8vw0Mqr60RcuwQ7LAEJAzh47mDYPccU=

### Vector B: POST with signed payload

- username: demo_user
- accessKey: 8370921743021
- secret: 321094343143
- utcDate: 20210118093334
- method: POST
- path: /td/booking-documents/1
- query: (empty string)
- body JSON:
   - {"Prop":"value"}
- expected bodyHash:
   - PoC1f5e+KKPQS04zib91ks+pcmKSjyjH1zG5MmwZ8iU=
- canonical:
   - 20210118093334POST/TD/BOOKING-DOCUMENTS/1PoC1f5e+KKPQS04zib91ks+pcmKSjyjH1zG5MmwZ8iU=
- expected signature:
   - oivHWZcvz433brve2oLckjxXq7fD29eMjd6lhLxm6QM=

---

## Example values from the C# sample

- username: demo_user
- accessKey: 8370921743021
- secret: 321094343143
- baseUrl: https://publicapi.stage.nt.cloud.bewotec.de

GET example:

- method: GET
- path: /td/travel-infos/4711
- query: ?q=100
- bodyHash: empty

POST example:

- method: POST
- path: /td/booking-documents/1
- query: empty
- body: { "Prop": "value" }
- includePayload: true

---

## C# implementation quality notes (recommended)

Compared to a minimal sample implementation, generated C# clients should prefer the following:

- Reuse HttpClient instances (do not create a new HttpClient per request).
- Use async end-to-end (avoid blocking calls like .Result on ReadAsByteArrayAsync).
- Serialize JSON once and use the same bytes for both bodyHash and HTTP body.
- Build request URI explicitly from baseUrl + path + query and sign exactly the same logical values.
- Keep signing code in a dedicated helper/service so it is testable with golden vectors.

---

## Common pitfalls

- Wrong time format (must be UTC and exactly yyyyMMddHHmmss)
- Path/query not uppercased for the canonical string
- Wrong character encoding (always UTF-8)
- Using SHA256 instead of HMACSHA256 for the signature
- Forgetting Base64 encoding
- Computing body hash from different JSON than what is actually sent
- Extra spaces in the Authorization header

---

## Quick checklist

- UTC date generated?
- Canonical string built correctly?
- HMACSHA256 computed with secret?
- Signature Base64-encoded?
- Authorization header set exactly in format DirectGrant ... ?
- For signed body, x-nt-content-sha256: true set?
