# Authentication — Portal Policy Template Bundles

This folder contains policy template bundles that **authenticate an inbound request against a Layer7 API Portal–managed API**. Each bundle installs an encapsulated assertion plus its backing policy fragment (under `/Portal Templates/`).

## Bundles in this folder

### `APIKeyValidation-1.0.0.json`
Installs the **`APIKeyValidation-1.0`** template. Validates that an incoming **API key** — supplied either in the `apikey` HTTP header or in the `apikey` query parameter — exists in the Portal, is bound to the managed API identified by `portal.managed.service.apiId`, and has a status of `active`. Returns **`401`** with `"API key is missing from request"` or `"API key not found or invalid"` on failure. `portal.analytics.authType` is set to `API_KEY`. **NOTE** This template does not provide true authentication and should only be used in cases where authentication is not required and the API Key is only used for tracking purposes.

### `APIKeySecretValidation-1.0.0.json`
Installs the **`APIKeySecretValidation1.0`** template. Validates an **API key + secret** pair supplied as **HTTP Basic credentials** (username = API key, password = secret). After confirming the key is found, scoped to this API, and `active`, it compares the request's secret against the value stored on the Portal API key record:

- If the API key record has no `hashingMetadata`, the plaintext secret is compared directly.
- If hashing metadata is present, the bundle calls **`Portal Read Hashed Metadata`** and **`Portal Create Hash`** to derive the expected hash (using the configured algorithm and salt) and compares against `apiKeyRecord.secret`.

Returns **`401`** with `"API key or secret missing from HTTP Basic credentials"`, `"API key not found or invalid"`, or `"Invalid API key secret"` on failure. `portal.analytics.authType` is set to `API_KEY`.

### `OAuth2Validation-1.0.0.json`
Installs the **`OAuth2Validation1.0`** template. Validates a Bearer **OAuth 2.0 access token** by invoking the Portal's stock **`Portal Require OAuth 2.0 Token`** verifier (no scope filtering, no one-time-token mode, no cache override). On success, the OAuth `client_id` (`${session.client_id}`) is used as the API key for the standard Portal lookup, ensuring the OAuth client is also a registered Portal application authorized for `portal.managed.service.apiId` and is `active`. Returns **`401`** with `"OAuth 2.0 token missing or invalid"` or `"OAuth client (API key) is not authorized for this API"` on failure. `portal.analytics.authType` is set to `OAUTH_2`.

### `JWTValidation-1.0.0.json`
Installs the **`JWTValidation-1.0`** template. Validates a JWT supplied as `Authorization: Bearer <token>` using gateway-stored **Recipient Keys** looked up by the JWT's `kid` header (`KeyGoid="0000000000000000ffffffffffffffff"` = "lookup any matching recipient key by kid"). After successful signature verification (`<L7p:DecodeJsonWebToken>` with `ValidationType="Using Recipient Key From List"`), the fragment validates:

1. **`iss`** claim equals the publisher-supplied **`expectedIssuer`** (case-sensitive equality).
2. **`aud`** claim CONTAINS the publisher-supplied **`expectedAudience`** (works for both string `aud` and array `aud`, since the JsonPath result for an array is its raw JSON array text).
3. **`scope`** claim CONTAINS the publisher-supplied **`allowedScopes`** (substring check; only enforced if `allowedScopes` is non-empty).

On any failure returns **`401`** with a JSON error body of the form `{"error":{"code":401,"message":"<reason>"}}`, sets `portal.analytics.response.code=401`, and short-circuits the policy with `<L7p:FalseAssertion/>`. On success sets `portal.analytics.authType=JWT` and `portal.analytics.apikey=${claim.sub.result}` (the JWT subject), then exports analytics + extracted-claim variables.

**Prerequisites**

- The IdP's signing certificate(s) (or JWKs converted to certificates) must be **pre-loaded into the gateway** via *Tasks > Manage Certificates*, with the **Recipient Key** role checked. Each loaded key must have a matching `kid` so the assertion can find the right one. For an automatic-rotation alternative that fetches a JWKS from the IdP at runtime, see "Customising the bundle" below.

**Arguments (inputs)**

| Visible / Hidden | Name | Notes |
| --- | --- | --- |
| hidden | `portal.managed.service.apiId` | Auto-populated by the Portal; used to set `portal.analytics.api.uuid`. |
| **visible** | `expectedIssuer *` | Expected JWT `iss` claim value (e.g. `https://my-idp.example.com/` for Okta/Auth0; the tenant URL for Azure AD). |
| **visible** | `expectedAudience *` | Expected JWT `aud` claim value (e.g. `api://orders` or the API's resource identifier). |
| **visible** | `allowedScopes` | *(Optional)* Space-separated list of OAuth-style scopes the JWT must include (substring check). Leave empty to skip scope validation. |

**Exported context variables (outputs):** `errorMsg`, `portal.analytics.action`, `portal.analytics.api.uuid`, `portal.analytics.apikey`, `portal.analytics.authType`, `portal.analytics.response.code`, `claim.iss.result`, `claim.aud.result`, `claim.scope.result`, `claim.sub.result`.

**Customising:**
- **Switch to JWKS-URL retrieval (auto-rotation).** Replace the single `<L7p:DecodeJsonWebToken>` (Recipient-Key variant) with the OTK pattern documented in [`Utilities/gateway-create-otk-customizations/.../#OTK Client JWT Validation`](../../../Utilities/gateway-create-otk-customizations) — namely (a) decode once with `ValidationType="None"` to extract `kid`, (b) `<L7p:HttpRoutingAssertion>` GET the JWKS URL into `${jwks}`, (c) decode again with `ValidationType="Using Recipient Key From Context Variable"`, `KeyType="JSON Web Key Set"`, `PrivateKeySource="${jwks}"`, `KeyId="${jwt.kid.result}"`. Add a `jwksUrl` encass arg for the publisher to supply the URL.
- **Strict per-scope membership.** The current scope check is a substring match (so `allowedScopes="read"` will accept a token whose scope is `"readwrite"`). For RFC-correct membership use a `<L7p:Regex>` in match mode with the pattern `\b<scope>\b` for each required scope, or restructure the comparison to split `${claim.scope.result}` and `${allowedScopes}` on whitespace.
- **Don't echo the failure detail.** The default error body includes the reason for ease of integration testing. For production, change the failure-branch `errorMsg` SetVariables to a generic message (e.g. `"Token validation failed."`) so a probing attacker cannot enumerate why their token was rejected.

### `mTLSValidation-1.0.0.json`
Installs the **`mTLSValidation1.0`** template. Layered authentication that combines API key validation with **mutual-TLS client-certificate pinning**. The fragment performs four steps:

1. **Require SSL/TLS transport** — if the request did not arrive over TLS the assertion returns **`403`** with `"SSL/TLS transport is required"`.
2. **Pick the API key** from the `apikey` header (preferred) or `apikey` query parameter.
3. **Look up the API key** via `Portal Look Up API Key (Generic)` and confirm it is found, scoped to this API, and `active`.
4. **Require a client certificate** (`SslAssertion` with `RequireClientAuthentication=true`) and verify it matches a certificate published in the API key's `CustomMetaData` **JWKS**. The fragment parses `apiKeyRecord.xml` → `CustomMetaData` → `jwks.keys`, base64-decodes each entry's `x5c[0]` into a certificate, and compares its SHA-256 thumbprint against `${request.ssl.clientCertificate.thumbprintSHA256}`. A match sets `certificateValidated=true`; otherwise the request is rejected with **`401`** `"Invalid client certificate"`.

`portal.analytics.authType` is set to `API_KEY`. This bundle is intended for APIs that need both an application identity (the API key) and cryptographic proof-of-possession of a registered client certificate.

## Picking the right bundle

| Use case | Bundle |
| --- | --- |
| Simple application identity via shared API key | `APIKeyValidation-1.0.0.json` |
| Stronger application identity using API key + secret (Basic auth, hashing-aware) | `APIKeySecretValidation-1.0.0.json` |
| User/application identity via standard OAuth 2.0 access tokens | `OAuth2Validation-1.0.0.json` |
| User/application identity via JWT bearer tokens from an external IdP (Okta, Azure AD, Auth0, AWS Cognito, etc.), validated against gateway-loaded recipient keys | `JWTValidation-1.0.0.json` |
| API key plus pinned client certificate (mTLS) | `mTLSValidation-1.0.0.json` |
