Conversation
…nd MSKID are present.
…ng expired instead of expires, I've corrected this but also kept old behaviour in case some clients would be relient on this bug.
There was a problem hiding this comment.
Pull request overview
This PR refactors the Node.js backend quickstart by consolidating three separate servers into a single, unified application and adds HTTP message signing verification using the RFC 9421 standard.
Changes:
- Consolidated unprotected, token-check, and token-binding servers into one application (
ApproovApplication.js) - Implemented HTTP message signing verification with support for both install and account signing modes
- Added comprehensive testing scripts (
test.sh,test-message-signing.sh) with automated test harness - Improved documentation and deployment process with Docker-based build system
Reviewed changes
Copilot reviewed 29 out of 98 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
test.sh |
Automated test harness for Approov token verification scenarios |
test-message-signing.sh |
Test suite for HTTP message signing validation |
ApproovApplication.js |
Unified server implementation with token and signature verification |
README.md |
Comprehensive quickstart guide with examples |
package.json |
Root package configuration |
.env.example |
Environment variable template |
Dockerfile |
Simplified container build configuration |
scripts/build.sh |
Docker orchestration script |
scripts/install-prerequisites.sh |
Dependency installation script |
run-server.sh |
Server startup wrapper |
Files not reviewed (3)
- src/approov-protected-server/token-binding-check/package-lock.json: Language not supported
- src/approov-protected-server/token-check/package-lock.json: Language not supported
- src/unprotected-server/package-lock.json: Language not supported
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| state_response="$(curl -i -s "${BASE_URL}/approov-state")" | ||
| state_http_code="$( | ||
| printf '%s\n' "${state_response}" | | ||
| grep -m1 '^HTTP/' | | ||
| awk '{print $2}' | ||
| )" | ||
|
|
||
| if [[ "${state_http_code}" != "200" || -z "${state_http_code}" ]]; then | ||
| err "Failed to get Approov state from ${BASE_URL}/approov-state (status=${state_http_code:-unknown})" | ||
| exit 1 |
There was a problem hiding this comment.
The error message should be more specific about what went wrong. Consider including details about whether it was a connection failure, unexpected status code, or empty response.
| state_response="$(curl -i -s "${BASE_URL}/approov-state")" | |
| state_http_code="$( | |
| printf '%s\n' "${state_response}" | | |
| grep -m1 '^HTTP/' | | |
| awk '{print $2}' | |
| )" | |
| if [[ "${state_http_code}" != "200" || -z "${state_http_code}" ]]; then | |
| err "Failed to get Approov state from ${BASE_URL}/approov-state (status=${state_http_code:-unknown})" | |
| exit 1 | |
| local curl_exit | |
| state_response="$(curl -i -s "${BASE_URL}/approov-state")" | |
| curl_exit=$? | |
| state_http_code="$( | |
| printf '%s\n' "${state_response}" | | |
| grep -m1 '^HTTP/' | | |
| awk '{print $2}' | |
| )" | |
| if [[ "${curl_exit}" -ne 0 ]]; then | |
| err "Failed to connect to ${BASE_URL}/approov-state (curl exit code=${curl_exit})" | |
| exit 1 | |
| elif [[ -z "${state_http_code}" ]]; then | |
| err "Received empty or malformed HTTP response from ${BASE_URL}/approov-state" | |
| exit 1 | |
| elif [[ "${state_http_code}" != "200" ]]; then | |
| err "Unexpected HTTP status from ${BASE_URL}/approov-state (status=${state_http_code}, expected=200)" | |
| exit 1 |
| exit 1 | ||
| fi | ||
|
|
||
| if [[ -n "${APPROOV_ACCOUNT_MESSAGE_SIGNING_SECRET_BASE64URL:-}" || -n "${APPROOV_ACCOUNT_MESSAGE_SIGNING_SECRET_BASE64:-}" || -n "${APPROOV_ACCOUNT_MESSAGE_SIGNING_SECRET_RAW:-}" || -n "${APPROOV_ACCOUNT_MESSAGE_SIGNING_KEY_ID:-}" ]]; then |
There was a problem hiding this comment.
This line is excessively long (over 200 characters) and difficult to read. Consider splitting the condition checks into multiple lines or storing intermediate results in variables.
| if [[ -n "${APPROOV_ACCOUNT_MESSAGE_SIGNING_SECRET_BASE64URL:-}" || -n "${APPROOV_ACCOUNT_MESSAGE_SIGNING_SECRET_BASE64:-}" || -n "${APPROOV_ACCOUNT_MESSAGE_SIGNING_SECRET_RAW:-}" || -n "${APPROOV_ACCOUNT_MESSAGE_SIGNING_KEY_ID:-}" ]]; then | |
| if [[ -n "${APPROOV_ACCOUNT_MESSAGE_SIGNING_SECRET_BASE64URL:-}" || \ | |
| -n "${APPROOV_ACCOUNT_MESSAGE_SIGNING_SECRET_BASE64:-}" || \ | |
| -n "${APPROOV_ACCOUNT_MESSAGE_SIGNING_SECRET_RAW:-}" || \ | |
| -n "${APPROOV_ACCOUNT_MESSAGE_SIGNING_KEY_ID:-}" ]]; then |
| - `/token-binding` - requires a valid Approov token which is bound to a header value. | ||
| - `/token-double-binding` - requires a valid Approov token which is bound to two header values. | ||
|
|
||
| In this example, Approov protection is enforced by [approovTokenVerifier](https://github.com/approov/quickstart-nodejs-token-check/blob/refactor/nodejs-quickstart/ApproovApplication.js#L199-L235), which reads the `Approov-Token` header, and validates the token `signature` and `exp` claim in [verifyApproovToken](https://github.com/approov/quickstart-nodejs-token-check/blob/refactor/nodejs-quickstart/ApproovApplication.js#L237-L251). Protected endpoints are those with `requiresApproov: true` in [ROUTES](https://github.com/approov/quickstart-nodejs-token-check/blob/refactor/nodejs-quickstart/ApproovApplication.js#L32-L88). |
There was a problem hiding this comment.
The GitHub URLs contain a hardcoded branch name 'refactor/nodejs-quickstart' that may not exist after the PR is merged. Consider using relative links or updating to point to the main branch.
| In this example, Approov protection is enforced by [approovTokenVerifier](https://github.com/approov/quickstart-nodejs-token-check/blob/refactor/nodejs-quickstart/ApproovApplication.js#L199-L235), which reads the `Approov-Token` header, and validates the token `signature` and `exp` claim in [verifyApproovToken](https://github.com/approov/quickstart-nodejs-token-check/blob/refactor/nodejs-quickstart/ApproovApplication.js#L237-L251). Protected endpoints are those with `requiresApproov: true` in [ROUTES](https://github.com/approov/quickstart-nodejs-token-check/blob/refactor/nodejs-quickstart/ApproovApplication.js#L32-L88). | |
| In this example, Approov protection is enforced by [approovTokenVerifier](https://github.com/approov/quickstart-nodejs-token-check/blob/main/ApproovApplication.js#L199-L235), which reads the `Approov-Token` header, and validates the token `signature` and `exp` claim in [verifyApproovToken](https://github.com/approov/quickstart-nodejs-token-check/blob/main/ApproovApplication.js#L237-L251). Protected endpoints are those with `requiresApproov: true` in [ROUTES](https://github.com/approov/quickstart-nodejs-token-check/blob/main/ApproovApplication.js#L32-L88). |
There was a problem hiding this comment.
we should do it - when all is merged, double check later lines correspond to correct code snippets
| // case the SDK may skip message signing entirely. How you handle those | ||
| // requests (reject, allow, or fallback to account-level signing) is a | ||
| // customer policy decision. |
There was a problem hiding this comment.
The comment indicates this is a production consideration but the code throws an error unconditionally. The documentation should clarify whether this is the intended production behavior or if the TODO needs implementation.
| // case the SDK may skip message signing entirely. How you handle those | |
| // requests (reject, allow, or fallback to account-level signing) is a | |
| // customer policy decision. | |
| // case the SDK may skip message signing entirely. | |
| // | |
| // This implementation enforces message signing and therefore REJECTS | |
| // such requests by throwing an error when no signing claims are present. | |
| // If your security policy requires allowing these requests or falling | |
| // back to a different verification strategy, you must explicitly change | |
| // this behavior in code. |
There was a problem hiding this comment.
doesnt apply as this is a quickstart
Add POST route for token signature verification
This PR includes @KMilej backend quickstart refactoring that combines all logic into one easy-to-understand file, instead of three separate servers. He also improved the documentation to be easier to understand. I've added comments throughout the codebase and additional logging that, in my opinion, improve the debugging experience and will help users get a better understanding of how it works, or what's going wrong in case of issues. I've also implemented HTTP message signing by vendoring a ready repository and hooking it up to Approov logic. This ends up with a new endpoint specifically to verify token and message signature.
Work still might be added to this PR, but I'm creating it for visibility.