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-JWT HTTP header
Custom AI Apps (iframe) — token in the URL hash fragment
Attention
JWT tokens are only issued for users with admin or member roles. Viewers do not receive tokens.
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)
Claim Type Description issstringIssuer — the platform origin (e.g. https://app.meingpt.com) substringSubject — the user's ID audstringAudience — the organization ID expnumberExpiration time (Unix timestamp, 1 hour from issue) iatnumberIssued at (Unix timestamp) jtistringUnique token ID (UUID, changes on every issue) emailstringUser's email address usernamestringUser's display name organizationNamestringOrganization name rolestring"admin" or "member"teamsarrayTeams 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" }
]
}
Public keys are available per organization:
https://app.meingpt.com/api/custom-apps/v1/jwks/{organizationId}
Node.js (jose) Python (PyJWT)
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;
}
+
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.
Open assistant settings
Navigate to your assistant's configuration and open the Tools section.
Add or edit a Custom MCP Server
Add a new Custom MCP Server or edit an existing one.
Enable JWT Identity Forwarding
Toggle Signed user identity on. The hint text confirms: "Sends a signed meinGPT JWT in header X-meinGPT-JWT."
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' });
}
});
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.
+
For embedded AI Apps, the token is passed in the URL hash fragment on initial load and refreshed via postMessage.
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' );
}
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_REFRESHApp → meinGPT — Request a fresh token MEINGPT_TOKEN_RESPONSEmeinGPT → App token, expiresAtNew signed JWT + expiry timestamp MEINGPT_TOKEN_ERRORmeinGPT → App errorHuman-readable error string
Note
meinGPT validates the postMessage origin against your configured embed URL. Messages from unexpected origins are ignored.
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);
}
});
+
Attention
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 accept postMessage events from your meinGPT instance origin