Identity Forwarding
Delegated authentication context propagation to third-party systems via meinGPT JWT
JWT Identity Forwarding
With JWT Identity Forwarding, meinGPT passes a signed JSON Web Token to third-party systems so they can securely identify the current user and organization.
This pattern is used in two areas:
- Custom MCP Servers — token in the
X-meinGPT-JWTHTTP header - Custom AI Apps (iframe) — token in the URL hash fragment
JWT tokens are only issued for users with admin or member roles. Viewers do not receive tokens.
Token details
| Property | Value |
|---|---|
| Algorithm | RS256 (RSA + SHA-256) |
| Lifetime | 1 hour |
| Header | Includes kid (key ID) for key rotation |
| Issuer | Your meinGPT instance origin (e.g. https://app.meingpt.com) |
Claims reference
| Claim | Type | Description |
|---|---|---|
iss | string | Issuer — the platform origin (e.g. https://app.meingpt.com) |
sub | string | Subject — the user's ID |
aud | string | Audience — the organization ID |
exp | number | Expiration time (Unix timestamp, 1 hour from issue) |
iat | number | Issued at (Unix timestamp) |
jti | string | Unique token ID (UUID, changes on every issue) |
email | string | User's email address |
username | string | User's display name |
organizationName | string | Organization name |
role | string | "admin" or "member" |
teams | array | Teams the user belongs to — each entry has id and name |
Example payload:
{
"iss": "https://app.meingpt.com",
"sub": "clx1234567890",
"aud": "clx0987654321",
"exp": 1719504000,
"iat": 1719500400,
"jti": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"email": "max@example.com",
"username": "Max Mustermann",
"organizationName": "Acme Corp",
"role": "admin",
"teams": [
{ "id": "clxteam001", "name": "Engineering" }
]
}Verification via JWKS
Public keys are available per organization:
https://app.meingpt.com/api/custom-apps/v1/jwks/{organizationId}import { createRemoteJWKSet, jwtVerify } from 'jose';
const JWKS = createRemoteJWKSet(
new URL('https://app.meingpt.com/api/custom-apps/v1/jwks/<organizationId>')
);
async function verifyMeinGptJwt(token: string) {
const { payload } = await jwtVerify(token, JWKS, {
issuer: 'https://app.meingpt.com',
audience: '<organizationId>',
});
console.log('User:', payload.email);
console.log('Role:', payload.role);
return payload;
}import jwt
from jwt import PyJWKClient
JWKS_URL = "https://app.meingpt.com/api/custom-apps/v1/jwks/<organizationId>"
jwks_client = PyJWKClient(JWKS_URL)
def verify_meingpt_jwt(token: str) -> dict:
signing_key = jwks_client.get_signing_key_from_jwt(token)
payload = jwt.decode(
token,
signing_key.key,
algorithms=["RS256"],
issuer="https://app.meingpt.com",
audience="<organizationId>",
)
return payloadTransport: Custom MCP Servers
When JWT Identity Forwarding is enabled for an MCP server, every request includes:
X-meinGPT-JWT: eyJhbGciOiJSUzI1NiIs...Tokens are cached within a chat session and automatically refreshed 30 seconds before expiry. Your server does not need to request new tokens — each MCP call arrives with a valid one.
How to enable
Navigate to your assistant's configuration and open the Tools section.
Add a new Custom MCP Server or edit an existing one.
Toggle JWT Authentication on. The hint text confirms: "Sends a signed meinGPT JWT in header X-meinGPT-JWT."
Reading the token on your server
Extract the header and verify it:
// Express middleware example
import { createRemoteJWKSet, jwtVerify } from 'jose';
const JWKS = createRemoteJWKSet(
new URL('https://app.meingpt.com/api/custom-apps/v1/jwks/<organizationId>')
);
app.use(async (req, res, next) => {
const token = req.headers['x-meingpt-jwt'];
if (!token) return res.status(401).json({ error: 'Missing JWT' });
try {
const { payload } = await jwtVerify(token, JWKS, {
issuer: 'https://app.meingpt.com',
audience: '<organizationId>',
});
req.user = payload;
next();
} catch {
res.status(401).json({ error: 'Invalid token' });
}
});Custom headers with JWT placeholder
As an alternative to the toggle, you can use the placeholder {{MEINGPT_JWT}} in any custom header value. This is useful when your server expects the token in a different header (e.g. Authorization: Bearer {{MEINGPT_JWT}}). The placeholder is replaced at runtime with the signed JWT.
Transport: Custom AI Apps (iframe)
For embedded AI Apps, the token is passed in the URL hash fragment on initial load and refreshed via postMessage.
Initial token
When meinGPT loads your app in an iframe, the URL looks like:
https://your-app.example.com/path#token=eyJhbGciOiJSUzI1NiIs...Extract it in your frontend:
function getInitialToken() {
const params = new URLSearchParams(window.location.hash.slice(1));
return params.get('token');
}Token refresh via postMessage
Tokens expire after 1 hour. Your app can request a fresh token at any time using postMessage:
1. Request a new token — send to the parent window:
window.parent.postMessage({ type: 'MEINGPT_TOKEN_REFRESH' }, '*');2. Listen for the response:
window.addEventListener('message', (event) => {
// Validate origin
if (event.origin !== 'https://app.meingpt.com') return;
if (event.data.type === 'MEINGPT_TOKEN_RESPONSE') {
// Success — store the new token
const { token, expiresAt } = event.data;
setToken(token);
scheduleRefresh(new Date(expiresAt));
}
if (event.data.type === 'MEINGPT_TOKEN_ERROR') {
// Token refresh failed
console.error('Token refresh failed:', event.data.error);
}
});Message types:
| Message | Direction | Fields | Description |
|---|---|---|---|
MEINGPT_TOKEN_REFRESH | App → meinGPT | — | Request a fresh token |
MEINGPT_TOKEN_RESPONSE | meinGPT → App | token, expiresAt | New signed JWT + expiry timestamp |
MEINGPT_TOKEN_ERROR | meinGPT → App | error | Human-readable error string |
meinGPT validates the postMessage origin against your configured embed URL. Messages from unexpected origins are ignored.
Full lifecycle example
let currentToken = null;
let refreshTimer = null;
// 1. Read initial token from URL hash
currentToken = new URLSearchParams(window.location.hash.slice(1)).get('token');
// 2. Schedule refresh (e.g. 5 minutes before expiry)
function scheduleRefresh(expiresAt) {
clearTimeout(refreshTimer);
const ms = new Date(expiresAt).getTime() - Date.now() - 5 * 60 * 1000;
if (ms > 0) {
refreshTimer = setTimeout(requestRefresh, ms);
}
}
// 3. Request refresh via postMessage
function requestRefresh() {
window.parent.postMessage({ type: 'MEINGPT_TOKEN_REFRESH' }, '*');
}
// 4. Handle response
window.addEventListener('message', (event) => {
if (event.origin !== 'https://app.meingpt.com') return;
if (event.data.type === 'MEINGPT_TOKEN_RESPONSE') {
currentToken = event.data.token;
scheduleRefresh(event.data.expiresAt);
}
});Security considerations
Always verify the JWT signature via the JWKS endpoint. Never trust the token payload without cryptographic verification.
- Verify the signature — use the JWKS endpoint, don't just base64-decode the payload
- Check
exp— reject expired tokens - Validate
aud— ensure the audience matches your expected organization ID - Use HTTPS — your endpoint must use TLS to protect the token in transit
- Don't log tokens — JWTs contain user PII (email, name); avoid writing them to application logs
- iframe apps: validate
event.origin— only acceptpostMessageevents from your meinGPT instance origin