04 · Pull-Login (Login with GPTBots Workspace / M-Auth)
Wenn die Erweiterungs-App der Entwickler:innen eine eigenständige Website ist und einen „Login with GPTBots Workspace"-Button anbieten möchte. Nach dem Klick gehen Nutzer:innen zur GPTBots-Anmeldung, wählen einen Workspace aus und werden dann mit ihrem Anmeldezustand und den Identitätsinformationen der angemeldeten Nutzer:innen zurückgebracht.
Es kommt OAuth2-Autorisierungscode + PKCE zum Einsatz: Der Browser erhält nur einen einmaligen code; das eigentliche wsa wird von Ihrem Backend mit code + PKCE-code_verifier eingetauscht, und wsa gelangt nie in Browser-URL / Verlauf / Referer – sicherer als Push.
Die Prüfung nach dem Erhalt des
wsaist exakt dieselbe wie bei Push (siehe 05). Dieser Artikel behandelt nur „wie man daswsaerhält".
1. End-to-End-Ablauf
sequenceDiagram
participant WS as Workspace-Seite „Erweiterungen"
participant GB as GPTBots-Plattform
participant FE as Erweiterungs-App-Landingpage
participant BE as Erweiterungs-App-Backend
Note over WS: Nutzer:in klickt App-Symbol
WS->>GB: POST sign-token
Note over GB: Prüft, ob Klickende:r Mitglied dieses workspace ist<br/>Signiert wsa mit dem Schlüssel (5-Minuten-JWT, aud=Ihr Host)
GB-->>WS: Gibt wsa zurück
WS->>FE: Öffnet app_home_url?wsa=JWT
Note over FE: consumeHandoff(), liest ?wsa=
FE->>BE: POST /session/exchange (mit wsa)
Note over BE: verifyWsa() → identity<br/>Baut eigene Session auf
BE-->>FE: identity
Note over FE: history.replaceState entfernt ?wsa=
Plattform-Endpunkte
| Zweck | Methode | Pfad |
|---|---|---|
| Autorisierungs-Einstieg (Browser-Navigation) | GET | /api/console/account/extension-app/authorize |
| Token-Austausch (Backend → Backend) | POST | /api/console/account/extension-app/token |
/authorize-Parameter
| Parameter | Pflicht | Beschreibung |
|---|---|---|
client_id |
Ja | Einstiegs-URL der Erweiterungs-App (app home URL) |
redirect_uri |
Ja | Callback-Adresse, deren Host mit client_id dieselbe Domain haben muss (gleiches scheme + host) |
state |
Ja | Zufälliger CSRF-String, wird beim Callback unverändert zur Prüfung zurückgegeben |
code_challenge |
Ja | base64url(sha256(code_verifier)), ohne Padding |
code_challenge_method |
Ja | Akzeptiert nur S256 (Groß-/Kleinschreibung beachten) |
workspace_id |
Nein | Vorausgewählter Workspace, überspringt die Organisationsauswahlseite |
/authorize leitet je nach Anmeldezustand per 302 weiter zu: GPTBots-Anmeldeseite (nicht angemeldet) / Organisationsauswahlseite (angemeldet, aber keine Organisation gewählt) / redirect_uri?code&state (Organisation gewählt).
/token-Anfrage und -Antwort
Request-Body (Backend-Aufruf):
{ "code": "…", "codeVerifier": "…" }
Erfolgsantwort:
{
"code": 0,
"msg": "OK",
"data": { "wsa": "eyJhbGciOiJIUzI1NiJ9...", "token_type": "Bearer", "expires_in": 300 }
}
SDK-Anbindung
Frontend: Anmeldung starten (Button-Klick)
import { startWorkspaceLogin } from '@gptbots/workspace-extension-sdk';
// Beim Klick auf „Login with GPTBots Workspace":
await startWorkspaceLogin({
authorizeUrl: 'https://www.gptbots.ai/api/console/account/extension-app/authorize',
clientId: 'https://app.example.com/land', // = Ihre registrierte app home URL
redirectUri: 'https://app.example.com/callback', // host muss dieselbe Domain wie clientId haben
// workspaceId: 'p-xxx', // optional: vorausgewählter Workspace, überspringt die Organisationsauswahlseite
// state: '...', // optional: standardmäßig automatisch generierter 16-Byte-Zufalls-CSRF-state
});
// Das SDK erledigt automatisch: PKCE generieren (verifier→challenge), verifier+state in sessionStorage speichern,
// prüfen, dass authorizeUrl eine absolute URL ist, einen sicheren Kontext verlangen (HTTPS/localhost) und dann zu /authorize weiterleiten
Frontend: Callback-Landingpage
sequenceDiagram
participant FE as Erweiterungs-App-Frontend
participant GB as GPTBots
participant BE as Erweiterungs-App-Backend
Note over FE: startWorkspaceLogin()<br/>generiert PKCE(verifier→challenge)<br/>speichert in sessionStorage, 302-Weiterleitung
FE->>GB: GET /authorize
alt Nicht angemeldet
GB-->>FE: 302 zur GPTBots-Anmeldeseite (bestehende Anmeldung wiederverwenden)
else Angemeldet, keine Organisation gewählt
GB-->>FE: 302 Organisationsauswahlseite
else Angemeldet, Organisation gewählt
Note over GB: Stellt einmaligen code aus (Redis,<br/>gebunden an account/project/app/redirect/challenge)
GB-->>FE: 302 redirect_uri?code&state
end
Note over FE: completeWorkspaceLogin()<br/>prüft state(CSRF), holt verifier
FE->>BE: POST {code, codeVerifier}
Note over BE: exchangeWorkspaceCode()
BE->>GB: POST /token {code, verifier}
Note over GB: Prüft code(einmalig GETDEL) + PKCE<br/>signiert wsa
GB-->>BE: Gibt wsa zurück
Note over BE: verifyWsa(wsa) → baut eigene Session auf
BE-->>FE: identity
Backend: wsa eintauschen und prüfen
import { exchangeWorkspaceCode, verifyWsa } from '@gptbots/workspace-extension-verify';
// POST /session/workspace-login { code, codeVerifier }
app.post('/session/workspace-login', async (req, res) => {
try {
const { wsa } = await exchangeWorkspaceCode({
tokenUrl: 'https://www.gptbots.ai/api/console/account/extension-app/token',
code: req.body.code,
codeVerifier: req.body.codeVerifier,
// timeoutMs: 10000, // Standard 10s, verhindert, dass eine langsame Plattformantwort Ihre Anfrage blockiert; 0 zum Deaktivieren übergeben
});
const identity = verifyWsa(wsa, {
secret: process.env.EXTENSION_APP_SECRET,
audience: 'app.example.com',
});
const sid = createSession(identity);
res.cookie('sid', sid, { httpOnly: true, secure: true, sameSite: 'lax' });
res.json(identity);
} catch (e) {
res.status(401).json({ error: String(e) });
}
});
Das zurückgegebene
wsafolgt demselben JWT-Vertrag wie bei Push; die Verwendung vonverifyWsaist wortwörtlich identisch.
Sicherheitseinschränkungen (Pflichtlektüre)
redirect_urimuss dieselbe Domain wie die registrierte App haben: scheme + host müssen exakt gleichclient_idsein./authorizeist ein Browser-Navigationsendpunkt und kann keinen JSON-Fehler zurückgeben; wennredirect_urifehlt / nicht http(s) ist / eine andere Domain hat, wird niemals zu einer ungeprüften Adresse gesprungen, sondern zur Organisationsauswahlseite mit?error=invalid_requestzurückgekehrt. Dies ist entscheidend, um Open Redirect / Token-Lecks zu verhindern.- PKCE verpflichtend: Akzeptiert nur
code_challenge_method=S256(Groß-/Kleinschreibung beachten),code_challenge = base64url(sha256(code_verifier))ohne Padding. Die aktuelle Version verwendet keinclient_secret; PKCE bindet „Session-Start" und „Session-Austausch" aneinander. - Einmaliger
code: In Redis gespeichert, TTL 10 Minuten, beim Austausch atomar konsumiert, nicht wiederholbar. Wiederholung/Ablauf meldet403209 invalid_grant, nicht übereinstimmendercode_verifiermeldet403210 invalid_verifier. state(CSRF): Das SDK speichertstateundcode_verifierimsessionStorage, beim Callback wird geprüft, obstateübereinstimmt, bevor fortgefahren wird.- Organisationsbereich: Bei der Autorisierung wird geprüft, ob das Konto Mitglied des gewählten Workspace ist und ob die App in dieser Organisation verfügbar ist (organisationseigene Erweiterungen sind nur für die zugehörige Organisation sichtbar); hat ein Organisationsadministrator eine App deaktiviert, wird für diese Organisation nichts signiert, selbst wenn sie noch im Plattformverzeichnis steht.
Fehlercodes der /token-Austauschphase
Strukturelle Fehler von
/authorize(client_id/redirect_urifehlt oder andere Domain,code_challengeungültig,code_challenge_methodnichtS256) geben kein JSON zurück, sondern leiten per 302 zur Organisationsauswahlseite mit?error=invalid_requestzurück. Die folgende Tabelle listet nur die JSON-Fehlercodes der/token-Phase auf.
| code | Bedeutung | Auslösebedingung |
|---|---|---|
403209 |
Invalid grant | code fehlt, ist abgelaufen oder wurde bereits verwendet (Wiederholung) |
403210 |
Invalid verifier | PKCE-code_verifier stimmt nicht mit code_challenge überein |
Werte von ?error= in der Callback-URL: invalid_request (struktureller Fehler) / access_denied (kein Mitglied oder App in dieser Organisation nicht verfügbar) / server_error (unerwarteter Fehler). Das completeWorkspaceLogin des SDK wirft es als WorkspaceLoginError('AuthorizeError').
Nächster Schritt: Ob Push oder Pull – lesen Sie zu Prüfung und Sicherheit 05-Token-Prüfung und Sicherheit; die vollständige API finden Sie unter 06-SDK-API-Referenz.
