Skip to content

Commit 31dfda3

Browse files
committed
Add httpYac sample with HMAC auth config
1 parent eaa7b44 commit 31dfda3

9 files changed

Lines changed: 231 additions & 0 deletions

File tree

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
GET /addresses
2+
Authorization: HMAC client:{{tokenId}} secret:{{tokenSecret}}
3+
Content-Type: application/json
4+
5+
HTTP/1.1 200 - OK
6+
connection: close
7+
content-type: application/json; charset=utf-8
8+
date: Mon, 13 Apr 2026 21:28:24 GMT
9+
server: Kestrel
10+
transfer-encoding: chunked
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
GET /users
2+
Authorization: HMAC client:{{tokenId}} secret:{{tokenSecret}}
3+
Content-Type: application/json
4+
5+
HTTP/1.1 200 - OK
6+
connection: close
7+
content-type: application/json; charset=utf-8
8+
date: Mon, 13 Apr 2026 21:29:36 GMT
9+
server: Kestrel
10+
transfer-encoding: chunked
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
POST /addresses
2+
Authorization: HMAC client:{{tokenId}} secret:{{tokenSecret}}
3+
Content-Type: application/json
4+
5+
{
6+
"line1": "{{$randomInt 100 200}} Main St",
7+
"line2": "Apt {{$randomInt 1 50}}",
8+
"city": "New York",
9+
"state": "NY",
10+
"zipCode": "10001"
11+
}
12+
13+
HTTP/1.1 200 - OK
14+
connection: close
15+
content-type: application/json; charset=utf-8
16+
date: Mon, 13 Apr 2026 21:32:44 GMT
17+
server: Kestrel
18+
transfer-encoding: chunked
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
POST /users
2+
Authorization: HMAC client:{{tokenId}} secret:{{tokenSecret}}
3+
Content-Type: application/json
4+
5+
{
6+
"first": "John",
7+
"last": "Doe",
8+
"email": "John.Doe@example.com"
9+
}
10+
11+
HTTP/1.1 200 - OK
12+
connection: close
13+
content-type: application/json; charset=utf-8
14+
date: Mon, 13 Apr 2026 21:35:29 GMT
15+
server: Kestrel
16+
transfer-encoding: chunked
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
GET /weather
2+
Content-Type: application/json
3+
4+
HTTP/1.1 200 - OK
5+
connection: close
6+
content-type: application/json; charset=utf-8
7+
date: Mon, 13 Apr 2026 21:36:50 GMT
8+
server: Kestrel
9+
transfer-encoding: chunked
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
GET /
2+
Content-Type: application/json
3+
4+
HTTP/1.1 200 - OK
5+
connection: close
6+
content-type: text/plain; charset=utf-8
7+
date: Mon, 13 Apr 2026 21:37:25 GMT
8+
server: Kestrel
9+
transfer-encoding: chunked
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
POST /weather
2+
Content-Type: application/json
3+
4+
{
5+
"date": "2025-08-05",
6+
"temperatureC": 25,
7+
"summary": "Sunny"
8+
}
9+
10+
HTTP/1.1 200 - OK
11+
connection: close
12+
content-type: application/json; charset=utf-8
13+
date: Mon, 13 Apr 2026 21:38:22 GMT
14+
server: Kestrel
15+
transfer-encoding: chunked

samples/Sample.HttpYac/README.md

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
# HashGate HttpYac Sample
2+
3+
[httpYac](https://httpyac.github.io/) collection demonstrating HMAC authentication with HashGate. Includes public and authenticated endpoint examples.
4+
5+
## Prerequisites
6+
7+
- [httpYac CLI](https://httpyac.github.io/guide/installation_cli) or the [VS Code extension](https://marketplace.visualstudio.com/items?itemName=anweber.vscode-httpyac)
8+
- Node.js (used by the config script to compute HMAC signatures)
9+
- A running HashGate sample server:
10+
11+
```bash
12+
dotnet run --project ../Sample.MinimalApi
13+
```
14+
15+
## Structure
16+
17+
| Folder | Description |
18+
| ---------------- | -------------------------------------------- |
19+
| `Public/` | Endpoints that do not require authentication |
20+
| `Authenticated/` | Endpoints secured with HMAC authentication |
21+
22+
## How It Works
23+
24+
The `httpyac.config.js` file registers a `replaceVariable` hook that intercepts `Authorization` headers matching the pattern:
25+
26+
```
27+
Authorization: HMAC client:{{tokenId}} secret:{{tokenSecret}}
28+
```
29+
30+
The hook automatically computes the HMAC-SHA256 signature and sets the required headers (`host`, `x-timestamp`, `x-content-sha256`, `x-nonce`) on each request.
31+
32+
## Usage
33+
34+
### VS Code Extension
35+
36+
1. Install the **httpYac** VS Code extension
37+
2. Open any `.http` file in this folder
38+
3. Click **Send** above the request
39+
40+
### CLI
41+
42+
```bash
43+
cd samples/Sample.HttpYac
44+
npx httpyac Authenticated/GetUsers.http --env local
45+
```
46+
47+
## Environment Variables
48+
49+
Defined in `httpyac.config.js` under the `local` environment:
50+
51+
| Variable | Description |
52+
| ------------- | ------------------------------------ |
53+
| `host` | Base URL of the sample API |
54+
| `tokenId` | HMAC client identifier |
55+
| `tokenSecret` | HMAC secret key for signing requests |
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
const crypto = require('crypto');
2+
const { URL } = require('url');
3+
4+
module.exports = {
5+
log: {
6+
level: 'trace',
7+
supportAnsiColors: true,
8+
prettyPrint: true,
9+
},
10+
request: {
11+
https: {
12+
rejectUnauthorized: false
13+
}
14+
},
15+
environments: {
16+
local: {
17+
host: "https://localhost:7134",
18+
tokenId: "SampleClient",
19+
tokenSecret: "ci3JaJZRDQGq6juXVvfp89TnAzS43ASaK/uB38R6ndzr7NN/Wlbstvg+2ZaI2qUVHkvvD3+hPvvzL58Z/bPq6A==",
20+
}
21+
},
22+
configureHooks: function (api) {
23+
api.hooks.replaceVariable.addHook('replaceHmacHeader', replaceHmacHeader);
24+
}
25+
}
26+
27+
function replaceHmacHeader(text, type, context) {
28+
if (type.toLowerCase() !== 'authorization')
29+
return text;
30+
31+
if (!text.startsWith('HMAC'))
32+
return text;
33+
34+
const { request } = context;
35+
if (!request)
36+
return text;
37+
38+
// format: HMAC client:myclient secret:mysecret
39+
const hmacRegex = /^hmac\s*client\s*[:=]\s*(?<client>\S+)[\s&]+secret\s*[:=]\s*(?<secret>\S+)\s*$/i;
40+
const match = hmacRegex.exec(text);
41+
if (!match || !match.groups)
42+
return text;
43+
44+
const client = match.groups.client;
45+
const secretKey = match.groups.secret;
46+
47+
const timestamp = Math.floor(Date.now() / 1000);
48+
49+
const url = new URL(request.url);
50+
const host = url.hostname;
51+
const pathAndQuery = url.pathname + url.search;
52+
53+
const method = request.method.toUpperCase();
54+
const body = request.body ? request.body.toString() : '';
55+
56+
// Create content hash
57+
const contentHash = crypto
58+
.createHash('sha256')
59+
.update(body, 'utf8')
60+
.digest('base64');
61+
62+
// Generate unique nonce
63+
const nonce = crypto.randomUUID();
64+
65+
// Create signed headers and string to sign
66+
const headerValues = `${host};${timestamp};${contentHash};${nonce}`;
67+
const stringToSign = `${method}\n${pathAndQuery}\n${headerValues}`;
68+
69+
// Generate signature
70+
const signature = crypto
71+
.createHmac('sha256', secretKey)
72+
.update(stringToSign, 'utf8')
73+
.digest('base64');
74+
75+
// Construct Authorization header
76+
const signedHeader = 'host;x-timestamp;x-content-sha256;x-nonce';
77+
const authorization = `HMAC Client=${client}&SignedHeaders=${signedHeader}&Signature=${signature}`;
78+
79+
// Set required headers
80+
request.headers['host'] = host;
81+
request.headers['x-timestamp'] = timestamp.toString();
82+
request.headers['x-content-sha256'] = contentHash;
83+
request.headers['x-nonce'] = nonce;
84+
85+
console.info('Generated HMAC Authorization Header:', authorization);
86+
// Return the new Authorization header
87+
return authorization;
88+
89+
}

0 commit comments

Comments
 (0)