---
title: "Identity Forwarding"
description: "Delegated authentication context propagation to third-party systems via meinGPT JWT"
canonical_url: "https://meingpt.com/en/docs/integrations/jwt-identity-forwarding"
language: en
---

# Identity Forwarding

## 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-JWT` HTTP 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:

```json
{
  "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}
```

### Node.js (jose)

```typescript

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;
}
```

### Python (PyJWT)

```python

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:

```javascript
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:

```javascript
window.parent.postMessage({ type: 'MEINGPT_TOKEN_REFRESH' }, '*');
```

**2. Listen for the response:**

```javascript
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

```javascript
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 accept `postMessage` events from your meinGPT instance origin

## Related docs

- [Custom MCP Server](/en/docs/integrations/custom-mcp-server)
- [Security & Authentication Proxy](/en/docs/integrations/custom-ai-apps/security-auth-proxy)
