From cc7b47f72c3bad7acc0e9c77e600370a49734ec2 Mon Sep 17 00:00:00 2001 From: thomas Date: Sat, 27 Jun 2026 22:06:21 +0200 Subject: [PATCH 1/3] Add JWT authentication tutorials and tests --- .../jwt/AbstractSecurityJwtTutorialTest.java | 26 ++++++ .../IssuingAndValidatingJwtsTutorialTest.java | 75 +++++++++++++++++ distribution/tutorials/README.md | 5 ++ .../security/jwt/10-Requesting-a-JWT.md | 44 ++++++++++ .../jwt/20-Issuing-and-Validating-JWTs.yaml | 81 +++++++++++++++++++ distribution/tutorials/security/jwt/README.md | 24 ++++++ distribution/tutorials/security/jwt/jwk.json | 14 ++++ .../tutorials/security/jwt/membrane.cmd | 24 ++++++ .../tutorials/security/jwt/membrane.sh | 21 +++++ 9 files changed, 314 insertions(+) create mode 100644 distribution/src/test/java/com/predic8/membrane/tutorials/security/jwt/AbstractSecurityJwtTutorialTest.java create mode 100644 distribution/src/test/java/com/predic8/membrane/tutorials/security/jwt/IssuingAndValidatingJwtsTutorialTest.java create mode 100644 distribution/tutorials/security/jwt/10-Requesting-a-JWT.md create mode 100644 distribution/tutorials/security/jwt/20-Issuing-and-Validating-JWTs.yaml create mode 100644 distribution/tutorials/security/jwt/README.md create mode 100644 distribution/tutorials/security/jwt/jwk.json create mode 100644 distribution/tutorials/security/jwt/membrane.cmd create mode 100755 distribution/tutorials/security/jwt/membrane.sh diff --git a/distribution/src/test/java/com/predic8/membrane/tutorials/security/jwt/AbstractSecurityJwtTutorialTest.java b/distribution/src/test/java/com/predic8/membrane/tutorials/security/jwt/AbstractSecurityJwtTutorialTest.java new file mode 100644 index 0000000000..b63c35dfc9 --- /dev/null +++ b/distribution/src/test/java/com/predic8/membrane/tutorials/security/jwt/AbstractSecurityJwtTutorialTest.java @@ -0,0 +1,26 @@ +/* Copyright 2026 predic8 GmbH, www.predic8.com + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. */ + +package com.predic8.membrane.tutorials.security.jwt; + +import com.predic8.membrane.tutorials.AbstractMembraneTutorialTest; + +public abstract class AbstractSecurityJwtTutorialTest extends AbstractMembraneTutorialTest { + + @Override + protected String getTutorialDir() { + return "security/jwt"; + } + +} diff --git a/distribution/src/test/java/com/predic8/membrane/tutorials/security/jwt/IssuingAndValidatingJwtsTutorialTest.java b/distribution/src/test/java/com/predic8/membrane/tutorials/security/jwt/IssuingAndValidatingJwtsTutorialTest.java new file mode 100644 index 0000000000..522ee662ea --- /dev/null +++ b/distribution/src/test/java/com/predic8/membrane/tutorials/security/jwt/IssuingAndValidatingJwtsTutorialTest.java @@ -0,0 +1,75 @@ +/* Copyright 2026 predic8 GmbH, www.predic8.com + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. */ + +package com.predic8.membrane.tutorials.security.jwt; + +import org.junit.jupiter.api.Test; + +import static io.restassured.RestAssured.given; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.notNullValue; + +public class IssuingAndValidatingJwtsTutorialTest extends AbstractSecurityJwtTutorialTest { + + @Override + protected String getTutorialYaml() { + return "20-Issuing-and-Validating-JWTs.yaml"; + } + + @Test + void issuesTokenAndProtectsResource() { + // 1) Wrong credentials are rejected by HTTP Basic authentication. + // @formatter:off + given() + .auth().preemptive().basic("alice", "wrong") + .when() + .post("http://localhost:2000/token") + .then() + .statusCode(401); + // @formatter:on + + // 2) The protected resource requires a token. + // @formatter:off + given() + .when() + .get("http://localhost:2000/resource") + .then() + .statusCode(400); + // @formatter:on + + // 3) The authenticated user gets a token whose "sub" is their own username. + // @formatter:off + String accessToken = + given() + .auth().preemptive().basic("alice", "alice-secret") + .when() + .post("http://localhost:2000/token") + .then() + .statusCode(200) + .body("token_type", equalTo("bearer")) + .body("expires_in", equalTo(300)) + .body("access_token", notNullValue()) + .extract().path("access_token"); + + given() + .header("Authorization", "Bearer " + accessToken) + .when() + .get("http://localhost:2000/resource") + .then() + .statusCode(200) + .body("client", equalTo("alice")) + .body("scopes", equalTo("read write")); + // @formatter:on + } +} diff --git a/distribution/tutorials/README.md b/distribution/tutorials/README.md index dd42c9e2f0..848e2d7861 100644 --- a/distribution/tutorials/README.md +++ b/distribution/tutorials/README.md @@ -35,6 +35,11 @@ Run and observe Membrane in production. Expose Membrane as an MCP server for AI clients, inspect recent API traffic, and protect the MCP endpoint with an API key. +## [Security / JWT](security/jwt) + +Issue signed JSON Web Tokens and protect an API by validating them. Covers the OAuth2 client-credentials flow, Bearer tokens, and signature/expiry/audience checks. + + ## [SOAP Web Services (Legacy)](soap) If you need to integrate legacy SOAP Web Services, this tutorial provides examples and practical guidance. diff --git a/distribution/tutorials/security/jwt/10-Requesting-a-JWT.md b/distribution/tutorials/security/jwt/10-Requesting-a-JWT.md new file mode 100644 index 0000000000..5680117aea --- /dev/null +++ b/distribution/tutorials/security/jwt/10-Requesting-a-JWT.md @@ -0,0 +1,44 @@ +# Requesting a JWT + +No setup required, just `curl`. Uses the public Membrane demo at `https://api.predic8.de`. + +Based on: + +## 1. Request a token + +```sh +curl -X POST https://api.predic8.de/demo/oauth2/token \ + -u "my-client:my-secret" \ + -d "grant_type=client_credentials" +``` + +```json +{"access_token":"eyJ0eXAiOiJKV1Qi...","token_type":"bearer","expires_in":300} +``` + +## 2. Inspect the token + +Paste the `access_token` into . A JWT has three parts `header.payload.signature`: + +- `sub` — subject (the client id) +- `aud` — audience (the API this token is for) +- `scopes` — permissions granted +- `exp` — expiry (300s) + +## 3. Call the protected resource + +```sh +curl https://api.predic8.de/demo/resource \ + -H "Authorization: Bearer eyJ0eXAiOiJKV1Qi..." +``` + +```json +{ "success": true, "user": "my-client", "scopes": "read write" } +``` + +Try it without the header — the request is rejected. + +## Next + +Continue with [20-Issuing-and-Validating-JWTs.yaml](20-Issuing-and-Validating-JWTs.yaml) +where Membrane issues and validates the tokens itself. diff --git a/distribution/tutorials/security/jwt/20-Issuing-and-Validating-JWTs.yaml b/distribution/tutorials/security/jwt/20-Issuing-and-Validating-JWTs.yaml new file mode 100644 index 0000000000..ca3f3747fd --- /dev/null +++ b/distribution/tutorials/security/jwt/20-Issuing-and-Validating-JWTs.yaml @@ -0,0 +1,81 @@ +# yaml-language-server: $schema=https://www.membrane-api.io/v7.2.4.json +# +# Tutorial: Issuing and Validating JWTs with Membrane +# +# Membrane acts as both token server and protected resource - no external server needed. +# +# 1.) Start Membrane: +# ./membrane.sh -c 20-Issuing-and-Validating-JWTs.yaml +# +# 2.) Request a token: +# curl -X POST localhost:2000/token -u "alice:alice-secret" +# +# {"access_token":"eyJ0eXAiOiJKV1Qi...","token_type":"bearer","expires_in":300} +# +# 3.) Paste the token into https://jwt.io - notice sub, aud, scopes, exp. +# +# 4.) Call the resource without a token (rejected): +# curl -i localhost:2000/resource +# +# 5.) Call it with the token: +# curl localhost:2000/resource -H "Authorization: Bearer eyJ0eXAiOiJKV1Qi..." +# +# {"client":"alice","scopes":"read write"} +# +# NOTE: jwk.json contains a demo key - generate your own for production. + +api: + port: 2000 + name: Token Server + path: + uri: /token + flow: + - basicAuthentication: + users: + - username: alice + password: alice-secret + - request: + # user() returns the authenticated username; jwtSign adds iat/exp/nbf. + - template: + contentType: application/json + src: | + { + "sub": ${user()}, + "aud": "demo-resource", + "scopes": "read write" + } + - jwtSign: + property: token + jwk: + location: jwk.json + - template: + contentType: application/json + src: | + { + "access_token": ${property.token}, + "token_type": "bearer", + "expires_in": 300 + } + - return: + status: 200 +--- +api: + port: 2000 + name: Protected Resource + path: + uri: /resource + flow: + - jwtAuth: + expectedAud: demo-resource + jwks: + jwks: + - location: jwk.json + - template: + contentType: application/json + src: | + { + "client": ${property.jwt.get("sub")}, + "scopes": ${property.jwt.get("scopes")} + } + - return: + status: 200 diff --git a/distribution/tutorials/security/jwt/README.md b/distribution/tutorials/security/jwt/README.md new file mode 100644 index 0000000000..494b033634 --- /dev/null +++ b/distribution/tutorials/security/jwt/README.md @@ -0,0 +1,24 @@ +# JWT Authentication Tutorial + +Learn how to protect an API with JSON Web Tokens (JWT). A client exchanges its +credentials for a short-lived, signed token and then uses that token as a Bearer +token on each request, while the gateway validates the signature, expiry and +audience on every call. + +Each step is explained directly in the configuration file, which is also the +Membrane config you run. If possible, use an editor with YAML support such as +Visual Studio Code or IntelliJ IDEA. + +The tutorials build on each other, from simple to advanced: + +1. [10-Requesting-a-JWT.md](10-Requesting-a-JWT.md) — a `curl`-only walkthrough of the + hosted [Membrane demo](https://www.membrane-api.io/jwt/jwt-api-authentication-authorization-tutorial.html): + request a token via the OAuth2 Client Credentials flow and use it to call a + protected API. Nothing to run locally. +2. [20-Issuing-and-Validating-JWTs.yaml](20-Issuing-and-Validating-JWTs.yaml) — let Membrane itself + issue and validate the tokens, fully offline. + +## Next Steps + +Start with [10-Requesting-a-JWT.md](10-Requesting-a-JWT.md), then run +[20-Issuing-and-Validating-JWTs.yaml](20-Issuing-and-Validating-JWTs.yaml). diff --git a/distribution/tutorials/security/jwt/jwk.json b/distribution/tutorials/security/jwt/jwk.json new file mode 100644 index 0000000000..80429e255f --- /dev/null +++ b/distribution/tutorials/security/jwt/jwk.json @@ -0,0 +1,14 @@ +{ + "p": "wP1ITsKxAWOO03eywdOj73T5Po1OFXZlFgzf1CJaf8D3piuxS0C5JSHTKTO354_r9cg2WyQ3nEqJ6YScV3-NfW8xXbiyMr5Xokzn7YpuB9dtby0veEn4w7JHChH5lV2fwrjH2iL6IIOLrND9D_Dxoc3mmLMaie0mTW9-UHGOunk", + "kty": "RSA", + "q": "ve7893s-DMjWEFZSsUaj0wQh9LicfSDBlUzISR5ojYTDphQ2RRm1b3TLHHM2okP79BXrX-Jq6NDVVVsi0DJmkvNkivNMxSuqvf8Qo1G6YI_NgEzGCM9Nq1MZkwbrBQDHfiHQIxm4Td_OE3LUi7mR7Zye4aJIu0mx9W33jIaAbG0", + "d": "Hqi5ZZvJT_-vfxMXduGlRk8mVXkhhkoigZM-faabmyYTaHnrcIzahg0JYaLIEaSz9UyTURzP36502skvKOJ11W_cKa2r9RXjU8VBrwK1pPiohDqGvaWjJlIoQ-YgJ3yM6snk8V0chbr4_sqJknaBYXlmEIeRD1kY_-OBNpSr2PqtMj3j6v3XlhvLTRbVPA6aVIiPLimX5m-Xmo5jsSOj10CIJ3RkCNaegogUfX8V0eUPYl2OsBzGWEpzpBOKt9ej4pHcidejmQpnjkBUSlDMdz0jzgQYcLlCg6v62JI66yMtFBDNTbKo0q5niNN0BWSQZH8vEL_crCSfAiGT2wqsoQ", + "e": "AQAB", + "use": "sig", + "kid": "membrane", + "qi": "tbgI_1rCyz5Xb4jO004io5k6x42O4WghFdbL3-rvYcLdVVlUf6tdncvN2pgykUYqBHneZoedzILSZsmdwZypGcZXgYTPFxx3j8bqRwOPNAYK-BBzM-WqQSU5TtgkUAa83YsJy10cLQsHHaasOFX0nPT77jZQo_HeezzIAH9tgXY", + "dp": "hTwXeHCG_Rt7lljT62aujfmmvV2Wo9CaFzAKMw0Ih5x0HJ-bhgWIDK-edZqEA3TkBUoU5LVLQzZeof3wZaPkzc0_OqHxPIEWRTFtCRyBvB4pKhD67cO733cr_jLMqSb6zdb9-oYdQucuPcAGhcPlPbzFz3QPBVvZDqrDfMv5Kpk", + "alg": "RS256", + "dq": "qqQMokwXc2T87bCgmqTcirkryLIT5leHlJtnVkn7pSminZOLLonqeDh2Qxk__IkX1DPdREgnxQPaptU6cdLWVTBXJH9yebLBs_F1AUZsLFUGTD6trTySi1odn_qXK-eHU8sNNHvnGg_5FYAVdXNDqDcOh6lFrv6G4_noblhpCQ", + "n": "jy8oj0NscvKCaawqk_f53p-iroACUxIz1ysSfwabydA22QDIbEtJ_Z7UNiW4dbdQUpzcsdUTG--Es7ECEAvxn3Q3jxMX7hU0n75s_KHcfm1yao508F913YuMmP2THtMmBiT0cFbxVHkJD_QvcwWPqTcjqcc4n7MYVeVZaLq0KJ-pz2Avb7a7fx0Ouk2pAgO4reFiR43T6wo_dyxcN92TjhbK3z8Qmox8kME-ZukNmDIAlm_UHzKupZM8cGotP3f42xeigUZXiVwDdAxQeZgV8mWNHVIYKdDYccQxHETu94jOPUQwXL-vVp8PHTeLqhY1oGcT2EJ6SEpH6e5NIvBxhQ" +} \ No newline at end of file diff --git a/distribution/tutorials/security/jwt/membrane.cmd b/distribution/tutorials/security/jwt/membrane.cmd new file mode 100644 index 0000000000..8d2d64e9cf --- /dev/null +++ b/distribution/tutorials/security/jwt/membrane.cmd @@ -0,0 +1,24 @@ +@echo off +setlocal EnableExtensions + +set "SCRIPT_DIR=%~dp0" +if "%SCRIPT_DIR:~-1%"=="\" set "SCRIPT_DIR=%SCRIPT_DIR:~0,-1%" + +set "dir=%SCRIPT_DIR%" + +:search_up +if exist "%dir%\LICENSE.txt" if exist "%dir%\scripts\run-membrane.cmd" goto found +for %%A in ("%dir%\..") do set "next=%%~fA" +if /I "%next%"=="%dir%" goto notfound +set "dir=%next%" +goto search_up + +:found +set "MEMBRANE_HOME=%dir%" +set "MEMBRANE_CALLER_DIR=%SCRIPT_DIR%" +call "%MEMBRANE_HOME%\scripts\run-membrane.cmd" %* +exit /b %ERRORLEVEL% + +:notfound +>&2 echo Could not locate Membrane root. Ensure directory structure is correct. +exit /b 1 diff --git a/distribution/tutorials/security/jwt/membrane.sh b/distribution/tutorials/security/jwt/membrane.sh new file mode 100755 index 0000000000..195dae51ec --- /dev/null +++ b/distribution/tutorials/security/jwt/membrane.sh @@ -0,0 +1,21 @@ +#!/bin/sh +# Default: ./proxies.xml (next to this script); fallback -> $MEMBRANE_HOME/conf/proxies.xml +# JAVA_OPTS: relative -D paths are auto-resolved against $MEMBRANE_HOME (absolute/URI unchanged). +# Examples: +# export JAVA_OPTS='-Dlog4j.configurationFile=examples/logging/access/log4j2_access.xml' +# export JAVA_OPTS='-Dlog4j.configurationFile=/abs/path/log4j2.xml' + +SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd -P) + +dir="$SCRIPT_DIR" +while [ "$dir" != "/" ]; do + if [ -f "$dir/LICENSE.txt" ] && [ -f "$dir/scripts/run-membrane.sh" ]; then + export MEMBRANE_HOME="$dir" + export MEMBRANE_CALLER_DIR="$SCRIPT_DIR" + exec sh "$dir/scripts/run-membrane.sh" "$@" + fi + dir=$(dirname "$dir") +done + +echo "Could not locate Membrane root. Ensure directory structure is correct." >&2 +exit 1 \ No newline at end of file From eb2298c5c571f7abecbe1dae73a869eb4824c9b1 Mon Sep 17 00:00:00 2001 From: thomas Date: Sun, 28 Jun 2026 09:16:16 +0200 Subject: [PATCH 2/3] Remove JWT-specific scripts and restructure security tutorials --- .../jwt/AbstractSecurityJwtTutorialTest.java | 2 +- .../IssuingAndValidatingJwtsTutorialTest.java | 2 +- distribution/tutorials/README.md | 2 +- ...esting-a-JWT.md => 40-Requesting-a-JWT.md} | 2 +- ...ml => 50-Issuing-and-Validating-JWTs.yaml} | 0 .../tutorials/security/{jwt => }/README.md | 8 +++---- .../tutorials/security/{jwt => }/jwk.json | 0 .../tutorials/security/jwt/membrane.cmd | 24 ------------------- .../tutorials/security/jwt/membrane.sh | 21 ---------------- 9 files changed, 8 insertions(+), 53 deletions(-) rename distribution/tutorials/security/{jwt/10-Requesting-a-JWT.md => 40-Requesting-a-JWT.md} (94%) rename distribution/tutorials/security/{jwt/20-Issuing-and-Validating-JWTs.yaml => 50-Issuing-and-Validating-JWTs.yaml} (100%) rename distribution/tutorials/security/{jwt => }/README.md (78%) rename distribution/tutorials/security/{jwt => }/jwk.json (100%) delete mode 100644 distribution/tutorials/security/jwt/membrane.cmd delete mode 100755 distribution/tutorials/security/jwt/membrane.sh diff --git a/distribution/src/test/java/com/predic8/membrane/tutorials/security/jwt/AbstractSecurityJwtTutorialTest.java b/distribution/src/test/java/com/predic8/membrane/tutorials/security/jwt/AbstractSecurityJwtTutorialTest.java index b63c35dfc9..132f60a05e 100644 --- a/distribution/src/test/java/com/predic8/membrane/tutorials/security/jwt/AbstractSecurityJwtTutorialTest.java +++ b/distribution/src/test/java/com/predic8/membrane/tutorials/security/jwt/AbstractSecurityJwtTutorialTest.java @@ -20,7 +20,7 @@ public abstract class AbstractSecurityJwtTutorialTest extends AbstractMembraneTu @Override protected String getTutorialDir() { - return "security/jwt"; + return "security"; } } diff --git a/distribution/src/test/java/com/predic8/membrane/tutorials/security/jwt/IssuingAndValidatingJwtsTutorialTest.java b/distribution/src/test/java/com/predic8/membrane/tutorials/security/jwt/IssuingAndValidatingJwtsTutorialTest.java index 522ee662ea..777f15b6da 100644 --- a/distribution/src/test/java/com/predic8/membrane/tutorials/security/jwt/IssuingAndValidatingJwtsTutorialTest.java +++ b/distribution/src/test/java/com/predic8/membrane/tutorials/security/jwt/IssuingAndValidatingJwtsTutorialTest.java @@ -24,7 +24,7 @@ public class IssuingAndValidatingJwtsTutorialTest extends AbstractSecurityJwtTut @Override protected String getTutorialYaml() { - return "20-Issuing-and-Validating-JWTs.yaml"; + return "50-Issuing-and-Validating-JWTs.yaml"; } @Test diff --git a/distribution/tutorials/README.md b/distribution/tutorials/README.md index 848e2d7861..99a6a0439e 100644 --- a/distribution/tutorials/README.md +++ b/distribution/tutorials/README.md @@ -35,7 +35,7 @@ Run and observe Membrane in production. Expose Membrane as an MCP server for AI clients, inspect recent API traffic, and protect the MCP endpoint with an API key. -## [Security / JWT](security/jwt) +## [Security](security) Issue signed JSON Web Tokens and protect an API by validating them. Covers the OAuth2 client-credentials flow, Bearer tokens, and signature/expiry/audience checks. diff --git a/distribution/tutorials/security/jwt/10-Requesting-a-JWT.md b/distribution/tutorials/security/40-Requesting-a-JWT.md similarity index 94% rename from distribution/tutorials/security/jwt/10-Requesting-a-JWT.md rename to distribution/tutorials/security/40-Requesting-a-JWT.md index 5680117aea..85277cc599 100644 --- a/distribution/tutorials/security/jwt/10-Requesting-a-JWT.md +++ b/distribution/tutorials/security/40-Requesting-a-JWT.md @@ -40,5 +40,5 @@ Try it without the header — the request is rejected. ## Next -Continue with [20-Issuing-and-Validating-JWTs.yaml](20-Issuing-and-Validating-JWTs.yaml) +Continue with [50-Issuing-and-Validating-JWTs.yaml](50-Issuing-and-Validating-JWTs.yaml) where Membrane issues and validates the tokens itself. diff --git a/distribution/tutorials/security/jwt/20-Issuing-and-Validating-JWTs.yaml b/distribution/tutorials/security/50-Issuing-and-Validating-JWTs.yaml similarity index 100% rename from distribution/tutorials/security/jwt/20-Issuing-and-Validating-JWTs.yaml rename to distribution/tutorials/security/50-Issuing-and-Validating-JWTs.yaml diff --git a/distribution/tutorials/security/jwt/README.md b/distribution/tutorials/security/README.md similarity index 78% rename from distribution/tutorials/security/jwt/README.md rename to distribution/tutorials/security/README.md index 494b033634..aed07bea32 100644 --- a/distribution/tutorials/security/jwt/README.md +++ b/distribution/tutorials/security/README.md @@ -11,14 +11,14 @@ Visual Studio Code or IntelliJ IDEA. The tutorials build on each other, from simple to advanced: -1. [10-Requesting-a-JWT.md](10-Requesting-a-JWT.md) — a `curl`-only walkthrough of the +1. [40-Requesting-a-JWT.md](40-Requesting-a-JWT.md) — a `curl`-only walkthrough of the hosted [Membrane demo](https://www.membrane-api.io/jwt/jwt-api-authentication-authorization-tutorial.html): request a token via the OAuth2 Client Credentials flow and use it to call a protected API. Nothing to run locally. -2. [20-Issuing-and-Validating-JWTs.yaml](20-Issuing-and-Validating-JWTs.yaml) — let Membrane itself +2. [50-Issuing-and-Validating-JWTs.yaml](50-Issuing-and-Validating-JWTs.yaml) — let Membrane itself issue and validate the tokens, fully offline. ## Next Steps -Start with [10-Requesting-a-JWT.md](10-Requesting-a-JWT.md), then run -[20-Issuing-and-Validating-JWTs.yaml](20-Issuing-and-Validating-JWTs.yaml). +Start with [40-Requesting-a-JWT.md](40-Requesting-a-JWT.md), then run +[50-Issuing-and-Validating-JWTs.yaml](50-Issuing-and-Validating-JWTs.yaml). diff --git a/distribution/tutorials/security/jwt/jwk.json b/distribution/tutorials/security/jwk.json similarity index 100% rename from distribution/tutorials/security/jwt/jwk.json rename to distribution/tutorials/security/jwk.json diff --git a/distribution/tutorials/security/jwt/membrane.cmd b/distribution/tutorials/security/jwt/membrane.cmd deleted file mode 100644 index 8d2d64e9cf..0000000000 --- a/distribution/tutorials/security/jwt/membrane.cmd +++ /dev/null @@ -1,24 +0,0 @@ -@echo off -setlocal EnableExtensions - -set "SCRIPT_DIR=%~dp0" -if "%SCRIPT_DIR:~-1%"=="\" set "SCRIPT_DIR=%SCRIPT_DIR:~0,-1%" - -set "dir=%SCRIPT_DIR%" - -:search_up -if exist "%dir%\LICENSE.txt" if exist "%dir%\scripts\run-membrane.cmd" goto found -for %%A in ("%dir%\..") do set "next=%%~fA" -if /I "%next%"=="%dir%" goto notfound -set "dir=%next%" -goto search_up - -:found -set "MEMBRANE_HOME=%dir%" -set "MEMBRANE_CALLER_DIR=%SCRIPT_DIR%" -call "%MEMBRANE_HOME%\scripts\run-membrane.cmd" %* -exit /b %ERRORLEVEL% - -:notfound ->&2 echo Could not locate Membrane root. Ensure directory structure is correct. -exit /b 1 diff --git a/distribution/tutorials/security/jwt/membrane.sh b/distribution/tutorials/security/jwt/membrane.sh deleted file mode 100755 index 195dae51ec..0000000000 --- a/distribution/tutorials/security/jwt/membrane.sh +++ /dev/null @@ -1,21 +0,0 @@ -#!/bin/sh -# Default: ./proxies.xml (next to this script); fallback -> $MEMBRANE_HOME/conf/proxies.xml -# JAVA_OPTS: relative -D paths are auto-resolved against $MEMBRANE_HOME (absolute/URI unchanged). -# Examples: -# export JAVA_OPTS='-Dlog4j.configurationFile=examples/logging/access/log4j2_access.xml' -# export JAVA_OPTS='-Dlog4j.configurationFile=/abs/path/log4j2.xml' - -SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd -P) - -dir="$SCRIPT_DIR" -while [ "$dir" != "/" ]; do - if [ -f "$dir/LICENSE.txt" ] && [ -f "$dir/scripts/run-membrane.sh" ]; then - export MEMBRANE_HOME="$dir" - export MEMBRANE_CALLER_DIR="$SCRIPT_DIR" - exec sh "$dir/scripts/run-membrane.sh" "$@" - fi - dir=$(dirname "$dir") -done - -echo "Could not locate Membrane root. Ensure directory structure is correct." >&2 -exit 1 \ No newline at end of file From 4f4bbf1e45323de4bc7f503931425c832a7da8c8 Mon Sep 17 00:00:00 2001 From: thomas Date: Sun, 28 Jun 2026 13:00:27 +0200 Subject: [PATCH 3/3] Use public JWK for JWT validation in security tutorial --- .../security/50-Issuing-and-Validating-JWTs.yaml | 5 +++-- distribution/tutorials/security/jwk-public.json | 8 ++++++++ 2 files changed, 11 insertions(+), 2 deletions(-) create mode 100644 distribution/tutorials/security/jwk-public.json diff --git a/distribution/tutorials/security/50-Issuing-and-Validating-JWTs.yaml b/distribution/tutorials/security/50-Issuing-and-Validating-JWTs.yaml index ca3f3747fd..a89cfc5c54 100644 --- a/distribution/tutorials/security/50-Issuing-and-Validating-JWTs.yaml +++ b/distribution/tutorials/security/50-Issuing-and-Validating-JWTs.yaml @@ -22,7 +22,8 @@ # # {"client":"alice","scopes":"read write"} # -# NOTE: jwk.json contains a demo key - generate your own for production. +# NOTE: jwk.json contains a demo key (private+public) - generate your own for production. +# jwk-public.json holds only the public parameters and is used by jwtAuth for validation. api: port: 2000 @@ -69,7 +70,7 @@ api: expectedAud: demo-resource jwks: jwks: - - location: jwk.json + - location: jwk-public.json - template: contentType: application/json src: | diff --git a/distribution/tutorials/security/jwk-public.json b/distribution/tutorials/security/jwk-public.json new file mode 100644 index 0000000000..f903b724a3 --- /dev/null +++ b/distribution/tutorials/security/jwk-public.json @@ -0,0 +1,8 @@ +{ + "kty": "RSA", + "e": "AQAB", + "use": "sig", + "kid": "membrane", + "alg": "RS256", + "n": "jy8oj0NscvKCaawqk_f53p-iroACUxIz1ysSfwabydA22QDIbEtJ_Z7UNiW4dbdQUpzcsdUTG--Es7ECEAvxn3Q3jxMX7hU0n75s_KHcfm1yao508F913YuMmP2THtMmBiT0cFbxVHkJD_QvcwWPqTcjqcc4n7MYVeVZaLq0KJ-pz2Avb7a7fx0Ouk2pAgO4reFiR43T6wo_dyxcN92TjhbK3z8Qmox8kME-ZukNmDIAlm_UHzKupZM8cGotP3f42xeigUZXiVwDdAxQeZgV8mWNHVIYKdDYccQxHETu94jOPUQwXL-vVp8PHTeLqhY1oGcT2EJ6SEpH6e5NIvBxhQ" +}