Skip to content
Merged
3 changes: 3 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ USAGE_SUBJECT_TYPE=api_key_user
# OIDC_SUBJECT_CLAIM=sub
# OIDC_SUBJECT_TYPE=oidc_user
# OIDC_REQUIRED_SCOPES=
# OIDC_TOKEN_EXCHANGE_BASE_URL= # optional; origin only (no path/query/hash/userinfo); https except loopback; enables app_<24hex>_<secret> exchange
# OIDC_EXCHANGE_M2M_CLIENT_ID=
# OIDC_EXCHANGE_M2M_CLIENT_SECRET=

# Reference only — not read by Compose services; for minting test JWTs.
# Canonical copy: auth0-provisioner/provision/.env.livepeer
Expand Down
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -152,13 +152,17 @@ Shared keys (`WEBHOOK_SECRET`, `KAFKA_BROKERS`, `KAFKA_GATEWAY_TOPIC`) are liste
| `DEMO_API_KEYS` | `api_key`, optional | JSON map of extra keys, e.g. `{"sk_other":{"clientId":"app-b","userId":"user-b"}}` |
| `USAGE_SUBJECT_TYPE` | `api_key`, optional | Default `api_key_user`; stamped on API-key identities |
| `API_KEY_PREFIX` | `api_key`, optional | Default `sk_` |
| `OIDC_ISSUER` | `oidc` | JWT issuer / JWKS host |
| `OIDC_ISSUER` | `oidc` | JWT issuer / JWKS host (trailing slash and surrounding whitespace are normalized) |
| `OIDC_AUDIENCE` | `oidc` | Expected JWT audience |
| `OIDC_JWKS_URI` | `oidc`, optional | Explicit JWKS override; otherwise resolved via OIDC Discovery (`${OIDC_ISSUER}/.well-known/openid-configuration` → `jwks_uri`) |
| `OIDC_CLIENT_CLAIM` | `oidc`, optional | Tenant claim (default `azp`; Auth0 clearinghouse uses `app_client_id`) |
| `OIDC_SUBJECT_CLAIM` | `oidc`, optional | End-user claim (default `sub`; Auth0 clearinghouse uses `external_user_id`) |
| `OIDC_SUBJECT_TYPE` | `oidc`, optional | Default `oidc_user`; Auth0 clearinghouse uses `external_user_id` |
| `OIDC_REQUIRED_SCOPES` | `oidc`, optional | Space- or comma-separated required scopes (e.g. `sign:job`) |
| `OIDC_TOKEN_EXCHANGE_BASE_URL` | `oidc`, optional | Origin only (no path/query/hash/userinfo). HTTPS required except loopback (`localhost` / `127.0.0.1` / `::1`). Enables composite `Bearer app_<24hex>_<secret>`: RFC 8693 exchange at `/api/v1/apps/{clientId}/oidc/token`, then JWT verify (`RS256`/`ES256`, required `exp`, 60s clock skew) |
| `OIDC_EXCHANGE_M2M_CLIENT_ID` / `OIDC_EXCHANGE_M2M_CLIENT_SECRET` | `oidc`, optional | Optional client credentials for the exchange request (both or neither) |

OIDC JWT verification accepts only `RS256` and `ES256` (asymmetric algorithms). Tokens must include `exp` and the configured subject claim.

`PORT` is set by Compose (`8090`), not `.env`. See the `identity-webhook` block in [`.env.example`](.env.example) for Auth0 vs generic OIDC examples.

Expand Down
6 changes: 3 additions & 3 deletions identity-webhook/protocol.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,10 @@ export class WebhookError extends Error {
}
}

/** Strip a `Bearer ` prefix (case-insensitive); returns the raw token otherwise. */
/** Extract the token from `Bearer <token>` (RFC 6750); empty if scheme is missing. */
export function bearerToken(authorization) {
const value = (authorization ?? "").trim();
return value.replace(/^Bearer\s+/i, "").trim();
const match = /^Bearer +([A-Za-z0-9._~+/-]+=*)$/i.exec((authorization ?? "").trim());
return match?.[1] ?? "";
}
Comment thread
eliteprox marked this conversation as resolved.

function timingSafeEqualStrings(a, b) {
Expand Down
16 changes: 14 additions & 2 deletions identity-webhook/protocol.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,24 @@ const fakeVerifier = {
const config = { webhookSecret: SECRET, endUserAuth: fakeVerifier };

describe("bearerToken", () => {
it("strips a case-insensitive Bearer prefix", () => {
it("requires a case-insensitive Bearer scheme", () => {
assert.equal(bearerToken("Bearer sk_abc"), "sk_abc");
assert.equal(bearerToken("bearer sk_abc"), "sk_abc");
assert.equal(bearerToken("sk_abc"), "sk_abc");
assert.equal(bearerToken("Bearer sk_abc"), "sk_abc");
assert.equal(bearerToken("sk_abc"), "");
assert.equal(bearerToken("Bearer"), "");
assert.equal(bearerToken("Bearer\tsk_abc"), "");
assert.equal(bearerToken(undefined), "");
});

it("rejects tokens with internal whitespace (RFC 6750 b64token)", () => {
assert.equal(bearerToken("Bearer a b"), "");
assert.equal(bearerToken("Bearer sk_abc extra"), "");
});

it("handles long malformed input without regular-expression backtracking", () => {
assert.equal(bearerToken(`NotBearer ${"a".repeat(100_000)}`), "");
});
});

describe("authenticateWebhookCaller", () => {
Expand Down
Loading
Loading