JWT-Authentifizierung für Custom Apps
Sichere Authentifizierung für Custom Apps in iframes mittels JWT-Tokens
Custom Apps, die in meinGPT über iframes eingebettet sind, können Benutzer mittels JWT (JSON Web Token) authentifizieren. Dies ermöglicht es deiner Custom App, den zugreifenden Benutzer sicher zu identifizieren und dessen Berechtigungen innerhalb der Organisation zu kennen.
Schnellstart
Token abrufen - Extrahiere ihn aus dem URL-Hash-Fragment:
const token = new URLSearchParams(window.location.hash.slice(1)).get('token');Token verifizieren - Verwende den JWKS-Endpunkt, um die Signatur auf deinem Backend zu verifizieren (siehe Beispiele unten).
Vor Ablauf erneuern - Tokens laufen nach 1 Stunde ab. Fordere eine Erneuerung via postMessage an.
Live-Demo
Verwende das interaktive Demo-Tool zum Testen der JWT-Verifizierung:
https://jwt-demo-iframe.selectcode.workers.devDie Demo:
- Verifiziert Signaturen mittels Web Crypto API und JWKS-Endpunkt
- Zeigt dekodierten Payload mit allen Claims (Benutzer, Rolle, Teams)
- Zeigt Verifizierungsstatus (gültige/ungültige Signatur)
- Liefert Code-Beispiele für Node.js, Python und Browser
Wenn die Demo in meinGPT eingebettet ist, empfängt und verifiziert sie automatisch den JWT-Token. Direkter Zugriff mit Token: https://jwt-demo-iframe.selectcode.workers.dev#token=eyJ...
Wichtige URLs
| Zweck | URL |
|---|---|
| JWKS-Endpunkt | https://app.meingpt.com/api/custom-apps/v1/jwks/{organizationId} |
| Demo-Tool | https://jwt-demo-iframe.selectcode.workers.dev |
Übersicht
Wenn ein Benutzer eine Custom App in meinGPT öffnet:
- meinGPT generiert einen signierten JWT-Token mit Benutzerinformationen
- Der Token wird über das URL-Fragment (Hash) an deine App übergeben
- Deine App verifiziert den Token mit dem öffentlichen Schlüssel der Organisation
- Der Token läuft nach 1 Stunde ab und kann per postMessage erneuert werden
Token-Struktur
Der JWT-Token enthält folgende Claims:
| Claim | Typ | Beschreibung |
|---|---|---|
iss | string | Issuer, immer https://app.meingpt.com |
sub | string | Subject - die eindeutige Benutzer-ID |
aud | string | Audience - die Organisations-ID |
exp | number | Ablaufzeit (Unix-Zeitstempel) |
iat | number | Ausstellungszeit (Unix-Zeitstempel) |
jti | string | Eindeutige Token-ID |
email | string | E-Mail-Adresse des Benutzers |
username | string | Anzeigename des Benutzers |
organizationName | string | Name der Organisation |
role | string | Rolle des Benutzers: admin oder member |
teams | array | Teams, denen der Benutzer angehört (ID und Name) |
Beispiel-Payload
{
"iss": "https://app.meingpt.com",
"sub": "user_abc123",
"aud": "org_xyz789",
"exp": 1704067200,
"iat": 1704063600,
"jti": "550e8400-e29b-41d4-a716-446655440000",
"email": "benutzer@beispiel.de",
"username": "Max Mustermann",
"organizationName": "Beispiel GmbH",
"role": "admin",
"teams": [
{ "id": "dept_001", "name": "Entwicklung" },
{ "id": "dept_002", "name": "Produkt" }
]
}Token empfangen
Der JWT-Token wird über das URL-Fragment (Hash) an deine App übergeben, welches nicht an deinen Server gesendet wird:
https://deine-app.de/embed#token=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...Token extrahieren (JavaScript)
// Token aus URL-Fragment extrahieren
function getTokenFromUrl() {
const hash = window.location.hash.substring(1); // '#' entfernen
const params = new URLSearchParams(hash);
return params.get('token');
}
const token = getTokenFromUrl();
if (token) {
// Token verifizieren und verwenden
const payload = await verifyToken(token);
console.log('Benutzer:', payload.email);
console.log('Rolle:', payload.role);
}Token verifizieren
Tokens werden mit RS256 (RSA-SHA256) signiert. Du solltest Tokens mit dem öffentlichen Schlüssel der Organisation verifizieren, der über den JWKS-Endpunkt verfügbar ist.
JWKS-Endpunkt
GET /api/custom-apps/v1/jwks/{organizationId}Antwort:
{
"keys": [
{
"kty": "RSA",
"use": "sig",
"alg": "RS256",
"kid": "key_abc123...",
"n": "...",
"e": "AQAB"
}
]
}Verifizierung mit Node.js
import jwt from 'jsonwebtoken';
import jwksClient from 'jwks-rsa';
const client = jwksClient({
jwksUri: `https://app.meingpt.com/api/custom-apps/v1/jwks/${organizationId}`
});
function getKey(header, callback) {
client.getSigningKey(header.kid, (err, key) => {
if (err) return callback(err);
const signingKey = key.publicKey || key.rsaPublicKey;
callback(null, signingKey);
});
}
function verifyToken(token) {
return new Promise((resolve, reject) => {
jwt.verify(token, getKey, {
issuer: 'https://app.meingpt.com',
audience: organizationId,
algorithms: ['RS256']
}, (err, decoded) => {
if (err) reject(err);
else resolve(decoded);
});
});
}Verifizierung mit jose (Modernes Node.js)
import * as jose from 'jose';
const JWKS = jose.createRemoteJWKSet(
new URL(`https://app.meingpt.com/api/custom-apps/v1/jwks/${organizationId}`)
);
async function verifyToken(token) {
const { payload, protectedHeader } = await jose.jwtVerify(token, JWKS, {
issuer: 'https://app.meingpt.com',
audience: organizationId,
});
return payload;
}
// Verwendung
const payload = await verifyToken(token);
console.log('Benutzer:', payload.email);
console.log('Rolle:', payload.role);Verifizierung im Browser / Edge Functions (Web Crypto API)
Für client-seitige oder Edge-Runtime-Verifizierung ohne externe Abhängigkeiten:
// Base64url zu Uint8Array dekodieren
function base64UrlDecode(str) {
const pad = str.length % 4;
const padded = pad ? str + '='.repeat(4 - pad) : str;
const base64 = padded.replace(/-/g, '+').replace(/_/g, '/');
const binary = atob(base64);
const bytes = new Uint8Array(binary.length);
for (let i = 0; i < binary.length; i++) {
bytes[i] = binary.charCodeAt(i);
}
return bytes;
}
// JWKS abrufen und Key nach kid finden
async function getPublicKey(issuer, organizationId, kid) {
const jwksUrl = `${issuer}/api/custom-apps/v1/jwks/${organizationId}`;
const response = await fetch(jwksUrl);
const jwks = await response.json();
const jwk = jwks.keys.find(k => k.kid === kid);
return crypto.subtle.importKey(
'jwk',
{ kty: jwk.kty, n: jwk.n, e: jwk.e, alg: 'RS256' },
{ name: 'RSASSA-PKCS1-v1_5', hash: 'SHA-256' },
false,
['verify']
);
}
// JWT-Signatur verifizieren
async function verifyToken(token) {
const [headerB64, payloadB64, signatureB64] = token.split('.');
const header = JSON.parse(atob(headerB64.replace(/-/g, '+').replace(/_/g, '/')));
const payload = JSON.parse(atob(payloadB64.replace(/-/g, '+').replace(/_/g, '/')));
// Public Key vom JWKS abrufen
const cryptoKey = await getPublicKey(payload.iss, payload.aud, header.kid);
// Signatur verifizieren
const data = new TextEncoder().encode(headerB64 + '.' + payloadB64);
const signature = base64UrlDecode(signatureB64);
const valid = await crypto.subtle.verify('RSASSA-PKCS1-v1_5', cryptoKey, signature, data);
if (!valid) throw new Error('Ungültige Signatur');
if (payload.exp < Date.now() / 1000) throw new Error('Token abgelaufen');
return payload;
}Verifizierung mit Python
import jwt
from jwt import PyJWKClient
jwks_url = f"https://app.meingpt.com/api/custom-apps/v1/jwks/{organization_id}"
jwks_client = PyJWKClient(jwks_url)
def verify_token(token: str) -> dict:
signing_key = jwks_client.get_signing_key_from_jwt(token)
return jwt.decode(
token,
signing_key.key,
algorithms=["RS256"],
issuer="https://app.meingpt.com",
audience=organization_id
)
# Verwendung
payload = verify_token(token)
print(f"Benutzer: {payload['email']}")
print(f"Rolle: {payload['role']}")Token erneuern
Tokens laufen nach 1 Stunde ab. Deine App kann einen neuen Token über die postMessage-API anfordern:
Erneuerung anfordern
// Neuen Token vom übergeordneten Fenster anfordern
function requestTokenRefresh() {
window.parent.postMessage({ type: 'MEINGPT_TOKEN_REFRESH' }, '*');
}
// Auf Antwort warten
window.addEventListener('message', (event) => {
// Origin validieren
if (!event.origin.includes('meingpt.com')) return;
if (event.data?.type === 'MEINGPT_TOKEN_RESPONSE') {
const newToken = event.data.token;
const expiresAt = new Date(event.data.expiresAt);
// Neuen Token speichern und verwenden
handleNewToken(newToken, expiresAt);
}
if (event.data?.type === 'MEINGPT_TOKEN_ERROR') {
console.error('Token-Erneuerung fehlgeschlagen:', event.data.error);
}
});
// Automatische Erneuerung vor Ablauf einrichten
function scheduleRefresh(expiresAt) {
const refreshTime = expiresAt.getTime() - Date.now() - 5 * 60 * 1000; // 5 Min vor Ablauf
if (refreshTime > 0) {
setTimeout(requestTokenRefresh, refreshTime);
}
}Sicherheits-Best-Practices
Verifiziere Tokens immer auf deinem Backend. Vertraue Token-Inhalten niemals ohne Verifizierung.
Token-Signatur verifizieren - Verifiziere die JWT-Signatur immer über den JWKS-Endpunkt. Überspringe die Verifizierung niemals.
Claims validieren - Prüfe die Claims iss (Issuer), aud (Audience/Organisation) und exp (Ablaufzeit).
Nur HTTPS verwenden - Deine Custom App muss über HTTPS bereitgestellt werden, um Tokens sicher zu empfangen.
postMessage-Origin validieren - Validiere beim Empfang von Token-Erneuerungsantworten, dass der Message-Origin der erwarteten meinGPT-Domain entspricht.
Token-Ablauf behandeln - Implementiere eine ordnungsgemäße Token-Erneuerungslogik und behandle abgelaufene Tokens angemessen.
Fehlerbehebung
Token nicht empfangen
- Stelle sicher, dass deine App in einem iframe innerhalb von meinGPT geladen wird
- Prüfe, dass das Custom App-Modul eine
embedUrlkonfiguriert hat - Verifiziere, dass das URL-Fragment von deiner App erhalten bleibt
Token-Verifizierung schlägt fehl
- Stelle sicher, dass du die korrekte Organisations-ID für den JWKS-Endpunkt verwendest
- Prüfe, ob der Token abgelaufen ist
- Verifiziere, dass du den RS256-Algorithmus zur Verifizierung verwendest
postMessage funktioniert nicht
- Bestätige, dass deine App über HTTPS läuft
- Prüfe, ob du auf die korrekten Message-Typen hörst
- Validiere den Message-Origin zur Sicherheit
Support
Wenn du Hilfe bei der Integration der JWT-Authentifizierung benötigst, kontaktiere uns unter support@meingpt.com.