Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
sources:
- id: proxy-wasm-sdk-as
ref: master
commit: 60f25c7bd35564e5bafb421be7f37aa4acf1bf81
updated: 2026-05-20
commit: 8e3bb621bc013a0aed7e52122066b417ad62a207
updated: 2026-08-17
-->

# A/B Testing — AssemblyScript (CDN)
Expand Down Expand Up @@ -101,6 +101,8 @@ set_property("request.url", String.UTF8.encode(newUrl))

`set_property` / `get_property` key: `"request.url"`, `"request.path"`, `"request.scheme"`, `"request.host"`, `"request.query"`.

URL rewrite is skipped entirely if `schemeBuf.byteLength === 0` or `hostBuf.byteLength === 0`.

### Step 5 — Add upstream headers

```
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
sources:
- id: proxy-wasm-sdk-as
ref: master
commit: 60f25c7bd35564e5bafb421be7f37aa4acf1bf81
updated: 2026-05-20
commit: 8e3bb621bc013a0aed7e52122066b417ad62a207
updated: 2026-08-17
-->

---
Expand All @@ -27,8 +27,8 @@ Validates incoming requests by checking the `X-API-Key` request header against a

## Entry Point

**File:** `assembly/index.ts`
**Root context name:** `"apiKey"`
**File:** `assembly/index.ts`
**Root context name:** `"apiKey"`
**Hook:** `onRequestHeaders`

---
Expand Down Expand Up @@ -164,7 +164,7 @@ Header `X-API-Key` is set to `""` on the forwarded request (platform `.remove()`

## Build

**Package manager:** `npm` (also compatible with `pnpm`)
**Package manager:** `npm` (also compatible with `pnpm`)
**SDK dependency:** `@gcoredev/proxy-wasm-sdk-as ^1.2.3`

```sh
Expand Down Expand Up @@ -199,3 +199,168 @@ Upload `build/apiKey.wasm` to the FastEdge portal and attach it to a CDN applica
- proxy-wasm-sdk-as reference (full `Context`, `RootContext`, `FilterHeadersStatusValues` API)
- FastEdge secrets management (how to set and rotate application secrets)
- CDN app deployment guide

## Source Material

### FILE: examples/apiKey/assembly/index.ts

```ts
export * from "@gcoredev/proxy-wasm-sdk-as/assembly/proxy";
import {
Context,
FilterHeadersStatusValues,
HeaderPair,
log,
LogLevelValues,
makeHeaderPair,
registerRootContext,
RootContext,
send_http_response,
stream_context,
} from "@gcoredev/proxy-wasm-sdk-as/assembly";
import {
getSecret,
setLogLevel,
} from "@gcoredev/proxy-wasm-sdk-as/assembly/fastedge";

const UNAUTHORIZED: u32 = 401;
const FORBIDDEN: u32 = 403;
const INTERNAL_SERVER_ERROR: u32 = 500;

class ApiKeyRoot extends RootContext {
createContext(context_id: u32): Context {
setLogLevel(LogLevelValues.info);
return new ApiKeyContext(context_id, this);
}
}

class ApiKeyContext extends Context {
constructor(context_id: u32, root_context: ApiKeyRoot) {
super(context_id, root_context);
}

onRequestHeaders(a: u32, end_of_stream: bool): FilterHeadersStatusValues {
const expectedKey = getSecret("API_KEY");
if (expectedKey === "") {
log(LogLevelValues.error, "API_KEY secret not configured");
send_http_response(
INTERNAL_SERVER_ERROR,
"internal server error",
String.UTF8.encode("App misconfigured"),
[],
);
return FilterHeadersStatusValues.StopIteration;
}

const providedKey = stream_context.headers.request.get("X-API-Key");

if (providedKey === "") {
const authHeaders = new Array<HeaderPair>();
authHeaders.push(makeHeaderPair("WWW-Authenticate", "API-Key"));
send_http_response(
UNAUTHORIZED,
"unauthorized",
String.UTF8.encode("Missing X-API-Key header"),
authHeaders,
);
return FilterHeadersStatusValues.StopIteration;
}

if (providedKey !== expectedKey) {
log(LogLevelValues.info, "API key validation failed");
send_http_response(
FORBIDDEN,
"forbidden",
String.UTF8.encode("Invalid API key"),
[],
);
return FilterHeadersStatusValues.StopIteration;
}

// .remove() sets the header value to "" rather than deleting it entirely —
// the upstream will see X-API-Key: "" rather than a missing header.
stream_context.headers.request.remove("X-API-Key");

log(LogLevelValues.info, "API key validated successfully");
return FilterHeadersStatusValues.Continue;
}
}

registerRootContext((context_id: u32) => {
return new ApiKeyRoot(context_id);
}, "apiKey");
```


### FILE: examples/apiKey/package.json

```json
{
"name": "fastedge-as-example-api-key",
"version": "1.0.0",
"description": "FastEdge AssemblyScript example: API Key — validate X-API-Key header against a secret",
"scripts": {
"asbuild:debug": "asc assembly/index.ts --target debug",
"asbuild:release": "asc assembly/index.ts --target release",
"asbuild": "npm run asbuild:debug && npm run asbuild:release"
},
"dependencies": {
"@gcoredev/proxy-wasm-sdk-as": "^1.2.3"
},
"devDependencies": {
"@assemblyscript/wasi-shim": "^0.1.0",
"assemblyscript": "^0.28.9"
}
}
```


### FILE: examples/apiKey/README.md

```
[← Back to examples](../README.md)

# API Key

This application validates requests using an `X-API-Key` header checked against a stored secret.

## What it does

In `onRequestHeaders`, the app:

1. Reads the expected API key from the `API_KEY` secret.
2. Checks the `X-API-Key` request header.
3. Returns `401 Unauthorized` if the header is missing.
4. Returns `403 Forbidden` if the key does not match.
5. On success, clears the `X-API-Key` header before forwarding to the upstream origin (proxy-wasm `.remove()` sets the header value to an empty string rather than deleting it).

This is a simpler alternative to JWT validation when you need basic API authentication without token expiry or claims.

> **Production note:** The key comparison (`providedKey !== expectedKey`) is not constant-time, which opens a timing side-channel for a high-volume attacker. For production use, replace the comparison with a constant-time HMAC equality check or use the `jwt` example which includes proper cryptographic validation.

## Configuration

Set the following on your FastEdge application:

| Name | Type | Description |
|------|------|-------------|
| `API_KEY` | Secret | The expected API key value |

## Build

```sh
pnpm install
pnpm run asbuild
```

Build output:

| File | Description |
|------|-------------|
| `build/apiKey.wasm` | Optimised release binary — upload this to FastEdge |
| `build/apiKey-debug.wasm` | Debug binary with source maps |

## Deploy

Upload `build/apiKey.wasm` to the FastEdge portal and attach it to your CDN application. Configure the `API_KEY` secret in the application settings.
```
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
sources:
- id: proxy-wasm-sdk-as
ref: master
commit: 60f25c7bd35564e5bafb421be7f37aa4acf1bf81
updated: 2026-05-20
commit: 8e3bb621bc013a0aed7e52122066b417ad62a207
updated: 2026-08-17
-->
---
capabilities:
Expand Down Expand Up @@ -62,7 +62,7 @@ Reads a named secret variable configured on the FastEdge application.

- **Parameter**: `name` — secret variable name (string)
- **Returns**: secret value as a UTF-8 string, or `null` if not found
- **Type note**: returns `string`, not `ArrayBuffer`; pass directly to `jwtVerify`
- **Type note**: returns `string`, not `ArrayBuffer`; pass directly to `jwtVerify` without encoding

```typescript
const secret = getSecret("SECRET");
Expand All @@ -82,6 +82,7 @@ Verifies a JWT token against an HMAC-SHA256 secret.
- `secret` — HMAC signing secret (string)
- **Returns**: `JwtValidation` enum value
- **Package**: `@gcoredev/as-jwt` (separate dependency, not part of proxy-wasm-sdk-as)
- **Behavior**: does not throw; always check the return value against `JwtValidation.Ok`

### `JwtValidation` enum

Expand All @@ -105,11 +106,11 @@ Sends an immediate HTTP response and stops the request. Body must be encoded as

### `setLogLevel(level: LogLevelValues): void`

Sets the log verbosity. Default is `LogLevelValues.info`. Called in `createContext`.
Sets the log verbosity. Default is `LogLevelValues.info`. Called in `createContext`. Present in source for demonstration purposes only — explicitly setting the default is optional.

### `log(level: LogLevelValues, message: string): void`

Emits a log entry. Used to record token rejection reasons.
Emits a log entry. Used to record token rejection reasons (e.g. `"Token Expired"`, `"Bad Token"`).

---

Expand Down Expand Up @@ -150,7 +151,23 @@ All blocked responses return `FilterHeadersStatusValues.StopIteration`.
|---|---|---|
| `SECRET` | HMAC-SHA256 signing key | String; minimum 256 bits / 32 characters |

Configure this secret variable on the FastEdge application before deployment. For secret rotation, see `getSecretEffectiveAt` in the FastEdge secrets reference.
Configure this secret variable on the FastEdge application before deployment. For secret rotation, use `getSecretEffectiveAt` instead of `getSecret` — see the FastEdge secrets reference.

---

## Testing Tokens

Both tokens use the secret `a-string-secret-at-least-256-bits-long-thats-hard-to-break`.

**Expired token** (returns `403 Forbidden`):
```
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJleHAiOjk3ODMxMDg2MX0.egSSDoDdAHz8Kqee7be9N168CDEwOiOej96Idm2c1yQ
```

**Valid token** (expiry: 2035-01-01, returns `200 OK`):
```
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJleHAiOjIwNTEyMjYwNjF9.zn_pSdcBo8T3SvNgMVYzWc5CU_MKqOlms7TpZXhPtJU
```

---

Expand All @@ -167,6 +184,14 @@ Configure this secret variable on the FastEdge application before deployment. Fo
- `@gcoredev/as-jwt` is a required peer dependency — it is NOT bundled in proxy-wasm-sdk-as.
- `assemblyscript-json` is declared as a dependency but not directly used in this example.

**Dev dependencies:**
```json
{
"@assemblyscript/wasi-shim": "^0.1.0",
"assemblyscript": "^0.28.9"
}
```

---

## Build
Expand Down Expand Up @@ -208,6 +233,7 @@ Root context name: `"auth"`.
- The `Authorization` header is validated in two steps: first a null check (missing header → 401), then a `startsWith("Bearer ")` check (wrong scheme → 401). An empty-string header would fail the Bearer scheme check.
- `jwtVerify` does not throw; always check the return value against `JwtValidation.Ok`.
- Validation happens in `onRequestHeaders` only. There is no body or response hook in this example.
- The `setLogLevel(LogLevelValues.info)` call in `createContext` is present for demonstration only — `info` is the default level and the call is not required.
- For HMAC secret rotation using slot-based secrets, use `getSecretEffectiveAt` instead of `getSecret`. See the FastEdge secrets reference.

---
Expand All @@ -217,4 +243,4 @@ Root context name: `"auth"`.
- proxy-wasm-sdk-as SDK reference (AssemblyScript)
- FastEdge secrets reference (`getSecret`, `getSecretEffectiveAt`, rotation slots)
- CDN app platform overview
- `@gcoredev/as-jwt` package (npmjs.com)
- `@gcoredev/as-jwt` package on npmjs.com
Loading
Loading