-
Notifications
You must be signed in to change notification settings - Fork 160
Document Cloud request signing #776
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 9 commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
51b7f64
Add Craft Cloud request signing docs
timkelty 8ffc90a
Clarify Cloud request signing docs
timkelty a407bd1
Fix request signing docs lint
timkelty c2367be
Clarify external request signing docs
timkelty 986926e
Clarify request signature verification policy
timkelty 22dccda
Move request verification guidance into examples
timkelty 786d9be
Tighten gateway signature expiration note
timkelty 7729524
Rename external request signing heading
timkelty 4451fea
Fix request signing intro wording
timkelty 02fc125
Rearrange, edit
AugustMiller File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,128 @@ | ||
| --- | ||
| description: Sign trusted programmatic requests to avoid bot rate limiting. | ||
| --- | ||
|
|
||
| # Request Signing | ||
|
|
||
| Request signing lets trusted systems make programmatic requests to Craft Cloud without being treated like unsanctioned bot traffic. | ||
|
|
||
| This is useful for automated systems like headless build processes or CI/CD pipelines, which can correctly look like bots and be rate-limited more aggressively than browsers. | ||
|
|
||
| When Cloud verifies a request signature, it treats the request as project-approved and bypasses bot-specific rate limiting. | ||
|
|
||
| Signatures use the environment’s `$CRAFT_CLOUD_SIGNING_KEY` to generate signatures. Treat this as a secret! | ||
|
|
||
| For more details on RFC 9421 HTTP Message Signatures, see [httpsig.org](https://httpsig.org/). | ||
|
|
||
| ## Signing Requests from Craft | ||
|
|
||
| The `craftcms/cloud` package can sign any PSR-7 request: | ||
|
|
||
| ```php | ||
| use craft\cloud\Module; | ||
| use GuzzleHttp\Client; | ||
| use GuzzleHttp\Psr7\Request; | ||
|
|
||
| $signer = Module::getInstance()->getRequestSigner(); | ||
|
|
||
| $request = new Request( | ||
| 'POST', | ||
| 'https://api.example.test/webhook', | ||
| ['Content-Type' => 'application/json'], | ||
| json_encode([ | ||
| 'event' => 'order.paid', | ||
| ], JSON_THROW_ON_ERROR), | ||
| ); | ||
|
|
||
| $signedRequest = $signer->sign($request); | ||
|
|
||
| $response = (new Client())->send($signedRequest); | ||
| ``` | ||
|
|
||
| To verify a signed PSR-7 request in Craft, use the same signing key: | ||
|
|
||
| ```php | ||
| use craft\helpers\App; | ||
| use HttpMessageSignatures\Algorithm\HmacSha256; | ||
| use HttpMessageSignatures\Verifier; | ||
|
|
||
| $isValid = (new Verifier(new HmacSha256(App::env('CRAFT_CLOUD_SIGNING_KEY')))) | ||
| ->verify($request); | ||
| ``` | ||
|
|
||
| ## Signing Requests Externally | ||
|
|
||
| External systems can generate valid signatures for a Craft Cloud environment, given the corresponding `$CRAFT_CLOUD_SIGNING_KEY`. | ||
|
|
||
| Signatures expire after 5 minutes when verified by the Craft Cloud gateway. Set `expires` about 5 minutes after `created`. | ||
|
|
||
| ### Node.js | ||
|
|
||
| This example signs a request with [`http-message-sig`](https://www.npmjs.com/package/http-message-sig): | ||
|
|
||
| ```bash | ||
| npm install http-message-sig | ||
| ``` | ||
|
|
||
| Then sign the request before sending it to Craft: | ||
|
|
||
| ```js | ||
| import crypto from 'node:crypto'; | ||
| import { signatureHeadersSync } from 'http-message-sig'; | ||
|
|
||
| const method = 'POST'; | ||
| const url = process.env.CRAFT_GRAPHQL_URL; | ||
|
|
||
| const body = JSON.stringify({ | ||
| query: ` | ||
| { | ||
| entries(section: "blog") { | ||
| title | ||
| url | ||
| } | ||
| } | ||
| `, | ||
| }); | ||
|
|
||
| const headers = { | ||
| 'Content-Type': 'application/json', | ||
| Authorization: `Bearer ${process.env.CRAFT_GRAPHQL_TOKEN}`, | ||
| }; | ||
|
|
||
| const created = new Date(); | ||
|
|
||
| const signer = { | ||
| keyid: 'hmac', | ||
| alg: 'hmac-sha256', | ||
| signSync(data) { | ||
| return crypto | ||
| .createHmac('sha256', process.env.CRAFT_CLOUD_SIGNING_KEY) | ||
| .update(data) | ||
| .digest(); | ||
| }, | ||
| }; | ||
|
|
||
| const signatureHeaders = signatureHeadersSync( | ||
| { method, url, headers, body }, | ||
| { | ||
| key: 'sig', | ||
| signer, | ||
| components: ['@method', '@target-uri'], | ||
| created, | ||
| expires: new Date(created.getTime() + 300_000), | ||
| }, | ||
| ); | ||
|
|
||
| const response = await fetch(url, { | ||
| method, | ||
| headers: { | ||
| ...headers, | ||
| ...signatureHeaders, | ||
| }, | ||
| body, | ||
| }); | ||
|
|
||
| const result = await response.json(); | ||
| ``` | ||
|
|
||
| Store the signing key in the external system’s secret manager. The `@target-uri` value must match the requested URL exactly, including any query string. | ||
|
AugustMiller marked this conversation as resolved.
Outdated
|
||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.