Skip to content

Commit a7ee23f

Browse files
committed
Switch to request-time HTTP message signing
1 parent da77b04 commit a7ee23f

15 files changed

Lines changed: 486 additions & 233 deletions

README.md

Lines changed: 85 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,90 @@ composer update "craftcms/cms:^5" "craftcms/flysystem:^2.0" --with-all-dependenc
5353

5454
## Developer Features
5555

56+
### Signed HTTP Requests
57+
58+
Use the module’s request signer to sign a PSR-7 request with Cloud’s signing key before sending it to any destination that can verify HTTP message signatures.
59+
60+
```php
61+
use craft\cloud\Module;
62+
use GuzzleHttp\Psr7\Request;
63+
64+
$signer = Module::getInstance()->getRequestSigner();
65+
66+
$signedRequest = $signer->sign(new Request('POST', 'https://example.test/webhook'));
67+
```
68+
69+
External systems can create compatible signatures without this PHP package. See
70+
[httpsig.org](https://httpsig.org/) for more information about HTTP message signatures. For example,
71+
install [`http-message-sig`](https://www.npmjs.com/package/http-message-sig) in a Node-based build
72+
environment:
73+
74+
```bash
75+
npm install http-message-sig
76+
```
77+
78+
Then a build script, e.g. on Vercel, can sign a Craft GraphQL request:
79+
80+
```js
81+
import crypto from 'node:crypto';
82+
import { signatureHeadersSync } from 'http-message-sig';
83+
84+
const method = 'POST';
85+
const url = process.env.CRAFT_GRAPHQL_URL;
86+
87+
const body = JSON.stringify({
88+
query: `
89+
{
90+
entries(section: "blog") {
91+
title
92+
url
93+
}
94+
}
95+
`,
96+
});
97+
98+
const headers = {
99+
'Content-Type': 'application/json',
100+
Authorization: `Bearer ${process.env.CRAFT_GRAPHQL_TOKEN}`,
101+
};
102+
103+
const signer = {
104+
keyid: 'hmac',
105+
alg: 'hmac-sha256',
106+
signSync(data) {
107+
return crypto
108+
.createHmac('sha256', process.env.CRAFT_CLOUD_SIGNING_KEY)
109+
.update(data)
110+
.digest();
111+
},
112+
};
113+
114+
const created = new Date();
115+
const signatureHeaders = signatureHeadersSync(
116+
{ method, url, headers, body },
117+
{
118+
key: 'sig',
119+
signer,
120+
components: ['@method', '@target-uri'],
121+
created,
122+
expires: new Date(created.getTime() + 300_000),
123+
},
124+
);
125+
126+
const response = await fetch(url, {
127+
method,
128+
headers: {
129+
...headers,
130+
...signatureHeaders,
131+
},
132+
body,
133+
});
134+
135+
const responseBody = await response.json();
136+
```
137+
138+
The `@target-uri` value must be the exact URL being requested, including any query string.
139+
56140
### Template Helpers
57141

58142
#### `cloud.artifactUrl()`
@@ -109,7 +193,7 @@ Most configuration (to Craft and the extension itself) is handled directly by Cl
109193
| `accessSecret` | `string` | AWS access secret, used in conjunction with the `accessKey`. |
110194
| `accessToken` | `string` | AWS access token. |
111195
| `redisUrl` | `string` | Connection string for the environment’s Redis instance. |
112-
| `signingKey` | `string` | A secret value used to protect transform URLs against abuse. |
196+
| `signingKey` | `string` | A secret value used to protect transform URLs and sign HTTP requests. |
113197
| `useAssetBundleCdn` | `boolean` | Whether or not to enable the CDN for asset bundles. |
114198
| `previewDomain` | `string\|null` | Set when accessing an environment from its [preview domain](https://craftcms.com/knowledge-base/cloud-domains#preview-domains). |
115199
| `useQueue` | `boolean` | Whether or not to use Cloud’s SQS-backed queue driver. |

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,14 @@
88
"bref/extra-php-extensions": "^3",
99
"craftcms/cms": "^4.6 || ^5",
1010
"craftcms/flysystem": "^1.0.0 || ^2.0.0",
11+
"craftcms/http-message-signatures": "^0.1",
1112
"guzzlehttp/guzzle": "^7.4.5",
1213
"league/flysystem-aws-s3-v3": "^3.15",
1314
"league/uri": "^7.6",
1415
"league/uri-components": "^7.6",
1516
"yiisoft/yii2-redis": "^2.0",
1617
"yiisoft/yii2-queue": "^2.3.7",
1718
"phlak/semver": "^4.1",
18-
"99designs/http-signatures": "^4.0",
1919
"symfony/process": "^6",
2020
"aws/aws-sdk-php": "^3.342.6",
2121
"craftcms/yii2-cache-cascade": "^1.2.1"

0 commit comments

Comments
 (0)