> ## Documentation Index
> Fetch the complete documentation index at: https://docs.aihubmix.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Integrationsleitfaden für Drittanbieter

## Wichtiger Sicherheitshinweis

Grundsatz „Sicherheit zuerst“: Bei der Entwicklung und Bereitstellung von Drittanbieteranwendungen ist Sicherheit der wichtigste Aspekt. Sie müssen die nachstehenden Sicherheitsprinzipien strikt einhalten, um die Sicherheit der Nutzerdaten und des Autorisierungsprozesses zu gewährleisten.

<Warning>
  1. Geben Sie `client_secret` niemals in JavaScript-Code im Frontend preis
  2. Der Austausch des OAuth-Authorization-Codes gegen ein Token muss serverseitig erfolgen
  3. Jeglicher Zugriff auf geschützte Nutzerressourcen muss über ein Backend-API-Proxy laufen
  4. Die gesamte OAuth-Kommunikation muss durch HTTPS geschützt werden
</Warning>

Ein Verstoß gegen eines der oben genannten Sicherheitsprinzipien kann zu schwerwiegenden Sicherheitslücken führen!

## 📋 Inhaltsverzeichnis

1. [Überblick](#overview)
2. [Erste Schritte](#getting-started)
3. [OAuth2-Autorisierungsablauf](#oauth2-authorization-flow)
4. [API-Referenz](#api-reference)
5. [SDK und Codebeispiele](#sdk-and-code-examples)
6. [Best Practices für die Sicherheit](#security-best-practices)
7. [FAQ](#faq)
8. [Technischer Support](#technical-support)

## Überblick

Wir bieten eine offene API auf Basis des OAuth2-Standards an, die es Drittanbieteranwendungen ermöglicht, sicher auf die Basisinformationen und den Kontostand der Nutzer zuzugreifen. Über unseren OAuth2-Dienst kann Ihre Anwendung:

* 🚀 Ein-Klick-Anmeldung: Nutzer müssen sich nicht erneut registrieren; die Anmeldung erteilt die Autorisierung automatisch und sorgt für ein wirklich nahtloses Erlebnis
* 👤 Nutzerinformationen abrufen: Zugriff auf das Basisprofil der Nutzer (Benutzername, E-Mail usw.)
* 💰 Kontostand einsehen: Den Kontostand der Nutzer in Echtzeit abrufen
* 🔄 Aufladungs-Weiterleitung: Nutzer zu unserer Aufladungsseite leiten, um das Konto aufzuladen
* 🔐 Automatische Token-Erneuerung: Eingebauter Refresh-Token-Mechanismus für eine nahtlose Token-Erneuerung und ein besseres Nutzererlebnis

## Erste Schritte

### 1. Entwicklerkonto registrieren

Zunächst müssen Sie ein Entwicklerkonto in unserem System registrieren.

### 2. OAuth-Anwendung erstellen

Erstellen Sie Ihre OAuth-Anwendung in der Entwicklerkonsole:

```shell API call example theme={null}
curl -X POST https://api.aihubmix.com/api/oauth_apps \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_DEVELOPER_TOKEN" \
  -d '{
    "name": "My Third-party App",
    "description": "App description",
    "redirect_uri": "https://yourapp.com/oauth/callback"
  }'
```

Beispielantwort:

```json theme={null}
{
  "success": true,
  "message": "Application created successfully",
  "data": {
    "id": 1,
    "name": "My Third-party App",
    "client_id": "client_abc123def456...",
    "client_secret": "secret_xyz789uvw012...",
    "redirect_uri": "https://yourapp.com/oauth/callback",
    "created_time": 1640995200
  }
}
```

Wichtiger Sicherheitshinweis:

<Warning>
  * client\_id darf im Frontend verwendet werden (öffentliche Information)
  * `client_secret` darf nur serverseitig verwendet und niemals im Browser offengelegt werden
  * Speichern Sie client\_secret in Umgebungsvariablen; codieren Sie es nicht fest in Ihren Code ein
</Warning>

### 3. Redirect-URI konfigurieren

Stellen Sie sicher, dass Ihre Redirect-URI die folgenden Anforderungen erfüllt:

* Verwendet das HTTPS-Protokoll (für die Produktion)
* Verweist auf einen Endpunkt Ihres Servers (keine Frontend-Seite)
* Die Domain ist registriert und erreichbar
* Der Pfad ist spezifisch für den API-Endpunkt, der den Callback verarbeitet

## OAuth2-Autorisierungsablauf

### Sicheres Ablaufdiagramm

```mermaid theme={null}
sequenceDiagram
    participant Browser  as User Browser
    participant Backend  as Your Server
    participant OAuthSrv as Our Server

    %% Login entry point
    Browser  ->> Backend : GET /auth<br>(user clicks login)

    %% Generate/save state, then redirect
    Backend  ->> Backend : Generate state<br>store state
    Backend  ->> OAuthSrv: Redirect to authorization<br>(with client_id)

    %% Show authorization page and complete
    OAuthSrv -->> Browser : Show authorization page
    Browser  ->> Browser : User completes authorization

    %% Authorization server calls back via browser
    Browser  ->> Backend : GET /callback
    Backend  ->> Backend : Validate state
    Backend  ->> OAuthSrv: Exchange code for token<br>(with secret)
    OAuthSrv -->> Backend : Validate and return token

    %% Save token and return to frontend
    Backend  ->> Backend : Store token
    Backend  -->> Browser : Redirect to frontend

    %% Fetch user info
    Browser  ->> Browser : Show user info
    Browser  ->> Backend : GET /api/user
    Backend  ->> OAuthSrv: Request user info with token
    OAuthSrv -->> Backend : Return user info
    Backend  -->> Browser : Return user info
```

### Schritt-für-Schritt-Anleitung

#### Schritt 1: Nutzer zur Autorisierung führen

Fügen Sie auf Ihrer Frontend-Seite eine Anmeldeschaltfläche hinzu. Beim Klick erfolgt eine Weiterleitung auf den serverseitigen Autorisierungs-Endpunkt:

```js // Frontend code - only responsible for redirecting theme={null}
function startLogin() {
    // Redirect to your server-side authorization handler
    window.location.href = '/auth/oauth/start';
}
```

#### Schritt 2: Serverseitiger Autorisierungs-Handler

Implementieren Sie den Autorisierungs-Handler auf Ihrem Server:

```js // Server-side code theme={null}
app.get('/auth/oauth/start', (req, res) => {
    // Generate and store state parameter
    const state = generateSecureRandomString();
    req.session.oauth_state = state;
    
    // Build authorization URL
    const authUrl = new URL('https://your-domain.com/api/oauth2/authorize');
    authUrl.searchParams.append('client_id', process.env.OAUTH_CLIENT_ID);
    authUrl.searchParams.append('redirect_uri', process.env.OAUTH_REDIRECT_URI);
    authUrl.searchParams.append('response_type', 'code');
    authUrl.searchParams.append('scope', 'profile balance');
    authUrl.searchParams.append('state', state);
    authUrl.searchParams.append('auto_authorize', 'true'); // One-click login
    
    // Redirect to authorization server
    res.redirect(authUrl.toString());
});
```

#### Schritt 3: Autorisierungs-Callback verarbeiten (serverseitig)

```js // Server-side authorization callback handler theme={null}
app.get('/oauth/callback', async (req, res) => {
    const { code, state, error } = req.query;
    
    // Error handling
    if (error) {
        return res.redirect(`/?error=${encodeURIComponent(error)}`);
    }
    
    // Parameter validation
    if (!code || !state) {
        return res.redirect('/?error=missing_parameters');
    }
    
    // Validate state parameter (prevent CSRF attacks)
    if (state !== req.session.oauth_state) {
        return res.redirect('/?error=invalid_state');
    }
    
    try {
        // Exchange authorization code for access token (server-side)
        const tokenResponse = await fetch('https://your-domain.com/api/oauth2/token', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/x-www-form-urlencoded'
            },
            body: new URLSearchParams({
                grant_type: 'authorization_code',
                code: code,
                redirect_uri: process.env.OAUTH_REDIRECT_URI,
                client_id: process.env.OAUTH_CLIENT_ID,
                client_secret: process.env.OAUTH_CLIENT_SECRET // Server-side only
            })
        });
        
        const tokenData = await tokenResponse.json();
        
        if (!tokenResponse.ok) {
            throw new Error(tokenData.error || 'Token exchange failed');
        }
        
        // Securely store token (server session or database)
        req.session.access_token = tokenData.access_token;
        req.session.refresh_token = tokenData.refresh_token;
        req.session.token_expires_at = Date.now() + (tokenData.expires_in * 1000);
        
        // Clean up temporary state
        delete req.session.oauth_state;
        
        // Redirect to frontend page
        res.redirect('/?login=success');
        
    } catch (error) {
        console.error('OAuth callback error:', error);
        res.redirect(`/?error=server_error`);
    }
});
```

#### Schritt 4: Frontend ruft Nutzerinformationen ab

```js // Frontend fetches user info via API proxy theme={null}
async function loadUserInfo() {
    try {
        const response = await fetch('/api/user/info');
        
        if (!response.ok) {
            throw new Error('Failed to fetch user info');
        }
        
        const userInfo = await response.json();
        displayUserInfo(userInfo);
        
    } catch (error) {
        console.error('Failed to load user info:', error);
        showLoginButton();
    }
}
```

```js // Server-side API proxy theme={null}
app.get('/api/user/info', async (req, res) => {
    const accessToken = req.session.access_token;
    
    if (!accessToken) {
        return res.status(401).json({ error: 'Not authenticated' });
    }
    
    try {
        // Proxy request to OAuth server
        const response = await fetch('https://your-domain.com/api/oauth2/userinfo', {
            headers: {
                'Authorization': `Bearer ${accessToken}`
            }
        });
        
        if (!response.ok) {
            throw new Error('User info request failed');
        }
        
        const userInfo = await response.json();
        res.json(userInfo);
        
    } catch (error) {
        console.error('User info proxy error:', error);
        res.status(500).json({ error: 'Server error' });
    }
});
```

<Tips>
  Ein-Klick-Anmeldung: Indem Sie `auto_authorize=true` setzen, erteilt das System nach der Anmeldung des Nutzers die Autorisierung automatisch, ohne zusätzliche Bestätigungsschritte.
</Tips>

## API-Referenz

### 1. Autorisierungs-Endpunkt

GET `/api/oauth2/authorize`

Führt den Nutzer durch die OAuth2-Autorisierung.

Parameter:

| Parameter        | Typ    | Erforderlich | Beschreibung                                                                   |
| ---------------- | ------ | ------------ | ------------------------------------------------------------------------------ |
| `client_id`      | string | Ja           | Client-ID der Anwendung (im Frontend verwendbar)                               |
| `redirect_uri`   | string | Ja           | Callback-URI nach der Autorisierung (muss auf einen Server-Endpunkt verweisen) |
| `response_type`  | string | Ja           | Fest: `code`                                                                   |
| `scope`          | string | Nein         | Berechtigungsumfang, durch Leerzeichen getrennt                                |
| `state`          | string | Ja           | Zufallszeichenfolge zur Abwehr von CSRF-Angriffen (serverseitig generiert)     |
| `auto_authorize` | string | Nein         | Auf `true` setzen, um die automatische Autorisierung zu aktivieren             |

Sicherheitsanforderungen:

* `redirect_uri` muss exakt mit der registrierten Adresse übereinstimmen
* `state` muss eine serverseitig generierte Zufallszeichenfolge sein
* HTTPS muss verwendet werden (in der Produktion)

Erläuterung der Scopes:

* `profile`: Zugriff auf die Basisinformationen des Nutzers (Benutzername, E-Mail)
* `balance`: Zugriff auf den Kontostand des Nutzers

### 2. Token-Endpunkt

POST `/api/oauth2/token`

<Warning>
  Sicherheitswarnung: Dieser Endpunkt darf ausschließlich serverseitig aufgerufen werden, niemals vom Frontend!
</Warning>

Wird in zwei Szenarien verwendet:

1. Austausch des Authorization-Codes gegen ein Access Token
2. Verwendung des Refresh Tokens zum Abrufen eines neuen Access Tokens

Parameter für die Authorization-Code-Vergabe:

| Parameter       | Typ    | Erforderlich | Beschreibung                                                      |
| --------------- | ------ | ------------ | ----------------------------------------------------------------- |
| `grant_type`    | string | Ja           | Fest: `authorization_code`                                        |
| `code`          | string | Ja           | Authorization-Code                                                |
| `redirect_uri`  | string | Ja           | Muss mit der bei der Autorisierung verwendeten URI übereinstimmen |
| `client_id`     | string | Ja           | Client-ID der Anwendung                                           |
| `client_secret` | string | Ja           | Client-Secret der Anwendung (nur serverseitig)                    |

Parameter für die Refresh-Token-Vergabe:

| Parameter       | Typ    | Erforderlich | Beschreibung                                   |
| --------------- | ------ | ------------ | ---------------------------------------------- |
| `grant_type`    | string | Ja           | Fest: `refresh_token`                          |
| `refresh_token` | string | Ja           | Refresh Token                                  |
| `client_id`     | string | Ja           | Client-ID der Anwendung                        |
| `client_secret` | string | Ja           | Client-Secret der Anwendung (nur serverseitig) |

Beispielantwort:

```json theme={null}
{
  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "token_type": "Bearer",
  "expires_in": 7200,
  "refresh_token": "refresh_abc123def456...",
  "scope": "profile balance"
}
```

### 3. Nutzerinformations-Endpunkt

GET `/api/oauth2/userinfo`

Ruft die Basisinformationen des Nutzers und den Kontostand ab.

Anforderungs-Header:

```json header theme={null}
Authorization: Bearer {access_token}
```

Beispielantwort:

```json response theme={null}
{
  "id": 12345,
  "username": "user123",
  "email": "user@example.com",
  "quota": 1000000,
  "used_quota": 250000,
  "balance_formatted": "750.00",
  "created_time": 1640995200,
  "status": 1
}
```

## SDK und Codebeispiele

### JavaScript-SDK

Wir stellen ein vollständiges JavaScript-SDK bereit, das Sie direkt verwenden können:

```html theme={null}
<!DOCTYPE html>
<html>
<head>
    <title>Third-party App Example</title>
    <style>
        .container { max-width: 800px; margin: 0 auto; padding: 20px; }
        .user-info { background: #f5f5f5; padding: 20px; border-radius: 8px; margin: 20px 0; }
        .login-section { text-align: center; padding: 40px; }
        .btn { padding: 12px 24px; border: none; border-radius: 6px; cursor: pointer; font-size: 16px; }
        .btn-primary { background: #007bff; color: white; }
        .btn-success { background: #28a745; color: white; }
        .btn-secondary { background: #6c757d; color: white; }
        .hidden { display: none; }
    </style>
</head>
<body>
    <div class="container">
        <h1>我的第三方应用</h1>
        
        <!-- Loading status -->
        <div id="loading">
            <p>Checking login status...</p>
        </div>
        
        <!-- Logged-in user info -->
        <div id="user-info" class="user-info hidden">
            <h2>Welcome back!</h2>
            <p><strong>Username:</strong> <span id="username"></span></p>
            <p><strong>Email:</strong> <span id="email"></span></p>
            <p><strong>Account Balance:</strong> <span id="balance"></span></p>
            <div style="margin-top: 20px;">
                <button class="btn btn-success" onclick="refreshBalance()">Refresh Balance</button>
                <button class="btn btn-primary" onclick="goToTopup()">Top Up Account</button>
                <button class="btn btn-secondary" onclick="logout()">Log Out</button>
            </div>
        </div>
        
        <!-- Not logged in status -->
        <div id="login-section" class="login-section hidden">
            <h2>Please log in to view account info</h2>
            <p>Use one-click login for quick access to your account</p>
            <button class="btn btn-primary" onclick="oneClickLogin()">
                🚀 One-click Login
            </button>
        </div>
    </div>

    <script>
        // OAuth configuration
        const OAUTH_CONFIG = {
            authServer: 'https://your-domain.com',
            clientId: 'YOUR_CLIENT_ID',
            clientSecret: 'YOUR_CLIENT_SECRET', // 生产环境应该在后端处理
            redirectUri: window.location.origin + '/oauth/callback.html',
            scope: 'profile balance'
        };

        class OAuthManager {
            constructor() {
                this.accessToken = localStorage.getItem('oauth_access_token');
                this.refreshToken = localStorage.getItem('oauth_refresh_token');
                this.tokenExpiresAt = localStorage.getItem('oauth_token_expires_at');
                this.isRefreshing = false; // 防止并发刷新
                this.init();
            }

            async init() {
                // 检查URL中是否有授权码
                const urlParams = new URLSearchParams(window.location.search);
                const code = urlParams.get('code');
                const state = urlParams.get('state');

                if (code) {
                    await this.handleAuthCallback(code, state);
                    // 清理URL
                    window.history.replaceState({}, document.title, window.location.pathname);
                } else if (this.accessToken) {
                    try {
                        // 检查token是否过期，如果过期尝试刷新
                        if (this.isTokenExpired()) {
                            await this.refreshTokenIfNeeded();
                        }
                        await this.fetchUserInfo();
                        this.showUserInfo();
                    } catch (error) {
                        console.log('Token可能已过期或无效，需要重新登录');
                        this.clearTokens();
                        this.showLoginSection();
                    }
                } else {
                    this.showLoginSection();
                }

                document.getElementById('loading').classList.add('hidden');
            }

            // Check if access token is expired
            isTokenExpired() {
                if (!this.tokenExpiresAt) return false;
                const expiryTime = parseInt(this.tokenExpiresAt);
                const bufferTime = 5 * 60 * 1000; // 5 minutes buffer
                return Date.now() > (expiryTime - bufferTime);
            }

            // Automatically refresh token
            async refreshTokenIfNeeded() {
                if (!this.refreshToken || this.isRefreshing) {
                    return false;
                }

                this.isRefreshing = true;
                
                try {
                    const response = await fetch(`${OAUTH_CONFIG.authServer}/api/oauth2/token`, {
                        method: 'POST',
                        headers: {
                            'Content-Type': 'application/x-www-form-urlencoded',
                        },
                        body: new URLSearchParams({
                            grant_type: 'refresh_token',
                            refresh_token: this.refreshToken,
                            client_id: OAUTH_CONFIG.clientId,
                            client_secret: OAUTH_CONFIG.clientSecret,
                        })
                    });

                    if (response.ok) {
                        const tokenData = await response.json();
                        this.updateTokens(tokenData);
                        return true;
                    } else {
                        throw new Error('Token refresh failed');
                    }
                } catch (error) {
                    console.error('Failed to refresh token:', error);
                    this.clearTokens();
                    return false;
                } finally {
                    this.isRefreshing = false;
                }
            }

            // Update token info
            updateTokens(tokenData) {
                this.accessToken = tokenData.access_token;
                this.refreshToken = tokenData.refresh_token;
                this.tokenExpiresAt = Date.now() + (tokenData.expires_in * 1000);

                localStorage.setItem('oauth_access_token', this.accessToken);
                localStorage.setItem('oauth_refresh_token', this.refreshToken);
                localStorage.setItem('oauth_token_expires_at', this.tokenExpiresAt.toString());
            }

            oneClickLogin() {
                const state = this.generateState();
                localStorage.setItem('oauth_state', state);

                const authUrl = `${OAUTH_CONFIG.authServer}/api/oauth2/authorize?` +
                    `client_id=${OAUTH_CONFIG.clientId}&` +
                    `redirect_uri=${encodeURIComponent(OAUTH_CONFIG.redirectUri)}&` +
                    `response_type=code&` +
                    `scope=${encodeURIComponent(OAUTH_CONFIG.scope)}&` +
                    `state=${state}&` +
                    `auto_authorize=true`; // Enable auto-authorization for true one-click login

                // Open authorization page in popup
                const popup = window.open(authUrl, 'oauth_login', 'width=500,height=600,scrollbars=yes');

                // Listen for popup close
                const checkClosed = setInterval(() => {
                    if (popup.closed) {
                        clearInterval(checkClosed);
                        // 检查是否获得了授权
                        setTimeout(() => this.init(), 1000);
                    }
                }, 1000);
            }

            async handleAuthCallback(code, state) {
                const savedState = localStorage.getItem('oauth_state');
                if (state !== savedState) {
                    console.error('State parameter mismatch');
                    return;
                }

                try {
                    const response = await fetch(`${OAUTH_CONFIG.authServer}/api/oauth2/token`, {
                        method: 'POST',
                        headers: {
                            'Content-Type': 'application/x-www-form-urlencoded',
                        },
                        body: new URLSearchParams({
                            grant_type: 'authorization_code',
                            code: code,
                            client_id: OAUTH_CONFIG.clientId,
                            client_secret: OAUTH_CONFIG.clientSecret,
                            redirect_uri: OAUTH_CONFIG.redirectUri
                        })
                    });

                    const tokenData = await response.json();
                    
                    if (tokenData.access_token) {
                        this.updateTokens(tokenData);
                        localStorage.removeItem('oauth_state');
                        
                        await this.fetchUserInfo();
                        this.showUserInfo();
                    }
                } catch (error) {
                    console.error('获取访问令牌失败:', error);
                }
            }

            // API request method with auto-refresh
            async apiRequest(url, options = {}) {
                // Check and refresh token if needed
                if (this.isTokenExpired()) {
                    const refreshed = await this.refreshTokenIfNeeded();
                    if (!refreshed) {
                        throw new Error('Unable to refresh token');
                    }
                }

                const headers = {
                    'Authorization': `Bearer ${this.accessToken}`,
                    ...options.headers
                };

                const response = await fetch(url, {
                    ...options,
                    headers
                });

                // If a 401 error is received, try to refresh token and retry once
                if (response.status === 401 && !options._retry) {
                    const refreshed = await this.refreshTokenIfNeeded();
                    if (refreshed) {
                        return this.apiRequest(url, { ...options, _retry: true });
                    }
                }

                return response;
            }

            async fetchUserInfo() {
                const response = await this.apiRequest(`${OAUTH_CONFIG.authServer}/api/oauth2/userinfo`);

                if (!response.ok) {
                    throw new Error('Failed to get user info');
                }

                this.userInfo = await response.json();
            }

            showUserInfo() {
                document.getElementById('username').textContent = this.userInfo.username;
                document.getElementById('email').textContent = this.userInfo.email;
                document.getElementById('balance').textContent = this.userInfo.balance_formatted;

                document.getElementById('user-info').classList.remove('hidden');
                document.getElementById('login-section').classList.add('hidden');
            }

            showLoginSection() {
                document.getElementById('user-info').classList.add('hidden');
                document.getElementById('login-section').classList.remove('hidden');
            }

            async refreshBalance() {
                try {
                    await this.fetchUserInfo();
                    document.getElementById('balance').textContent = this.userInfo.balance_formatted;
                    alert('Balance updated');
                } catch (error) {
                    alert('Refresh failed, please log in again');
                    this.clearTokens();
                    this.showLoginSection();
                }
            }

            async goToTopup() {
                try {
                    const response = await this.apiRequest(`${OAUTH_CONFIG.authServer}/api/oauth2/topup`);

                    const data = await response.json();
                    if (data.topup_url) {
                        window.open(data.topup_url, '_blank');
                    }
                } catch (error) {
                    console.error('Failed to get top-up link:', error);
                }
            }

            logout() {
                this.clearTokens();
                this.showLoginSection();
            }

            clearTokens() {
                this.accessToken = null;
                this.refreshToken = null;
                this.tokenExpiresAt = null;
                this.userInfo = null;
                
                localStorage.removeItem('oauth_access_token');
                localStorage.removeItem('oauth_refresh_token');
                localStorage.removeItem('oauth_token_expires_at');
            }

            generateState() {
                return Math.random().toString(36).substring(2, 15) + 
                       Math.random().toString(36).substring(2, 15);
            }
        }

        // 全局函数
        let oauthManager;

        function oneClickLogin() {
            oauthManager.oneClickLogin();
        }

        function refreshBalance() {
            oauthManager.refreshBalance();
        }

        function goToTopup() {
            oauthManager.goToTopup();
        }

        function logout() {
            oauthManager.logout();
        }

        // 页面加载完成后初始化
        document.addEventListener('DOMContentLoaded', () => {
            oauthManager = new OAuthManager();
        });
    </script>
</body>
</html>
```

### OAuth-Callback-Seite

Erstellen Sie eine Datei `/oauth/callback.html`:

```html theme={null}
<!DOCTYPE html>
<html>
<head>
    <title>Logging in...</title>
    <style>
        body { font-family: Arial, sans-serif; text-align: center; padding: 50px; }
        .loading { color: #666; }
        .error { color: #dc3545; }
    </style>
</head>
<body>
        <h2>Logging in, please wait...</h2>
        <div id="status" class="loading">Verifying authorization information...</div>

    <script>
        const urlParams = new URLSearchParams(window.location.search);
        const code = urlParams.get('code');
        const state = urlParams.get('state');
        const error = urlParams.get('error');

        if (error) {
            document.getElementById('status').innerHTML = 
                `<div class="error">Authorization failed: ${error}</div>`;
            setTimeout(() => {
                if (window.opener) {
                    window.close();
                } else {
                    window.location.href = '/';
                }
            }, 3000);
        } else if (code) {
            if (window.opener) {
                // 如果是弹窗，关闭并让父窗口处理
                window.opener.location.href = 
                    window.opener.location.pathname + `?code=${code}&state=${state}`;
                window.close();
            } else {
                // 如果不是弹窗，重定向到主页面
                window.location.href = `/?code=${code}&state=${state}`;
            }
        } else {
            document.getElementById('status').innerHTML = 
                '<div class="error">No valid authorization information received</div>';
            setTimeout(() => {
                window.location.href = '/';
            }, 3000);
        }
    </script>
</body>
</html>
```

### Node.js-Backend-Beispiel

Aus Sicherheitsgründen wird empfohlen, `client_secret` im Backend zu verarbeiten:

```js theme={null}
const express = require('express');
const cors = require('cors');
const fetch = require('node-fetch');

const app = express();
app.use(cors());
app.use(express.json());

const OAUTH_CONFIG = {
    authServer: 'https://your-domain.com',
    clientId: 'YOUR_CLIENT_ID',
    clientSecret: 'YOUR_CLIENT_SECRET'
};

// Backend handles token exchange
app.post('/api/oauth/exchange-token', async (req, res) => {
    const { code, redirectUri, state } = req.body;

    try {
        const response = await fetch(`${OAUTH_CONFIG.authServer}/api/oauth2/token`, {
            method: 'POST',
            headers: {
                'Content-Type': 'application/x-www-form-urlencoded',
            },
            body: new URLSearchParams({
                grant_type: 'authorization_code',
                code: code,
                client_id: OAUTH_CONFIG.clientId,
                client_secret: OAUTH_CONFIG.clientSecret,
                redirect_uri: redirectUri
            })
        });

        const tokenData = await response.json();
        
        if (response.ok) {
            res.json(tokenData);
        } else {
            res.status(400).json(tokenData);
        }
    } catch (error) {
        res.status(500).json({ error: 'Token exchange failed' });
    }
});

// Refresh token endpoint
app.post('/api/oauth/refresh-token', async (req, res) => {
    const { refreshToken } = req.body;

    if (!refreshToken) {
        return res.status(400).json({ error: 'Missing refresh token' });
    }

    try {
        const response = await fetch(`${OAUTH_CONFIG.authServer}/api/oauth2/token`, {
            method: 'POST',
            headers: {
                'Content-Type': 'application/x-www-form-urlencoded',
            },
            body: new URLSearchParams({
                grant_type: 'refresh_token',
                refresh_token: refreshToken,
                client_id: OAUTH_CONFIG.clientId,
                client_secret: OAUTH_CONFIG.clientSecret,
            })
        });

        const tokenData = await response.json();
        
        if (response.ok) {
            res.json(tokenData);
        } else {
            res.status(400).json(tokenData);
        }
    } catch (error) {
        res.status(500).json({ error: 'Token refresh failed' });
    }
});

// Proxy user info request (with auto-refresh)
app.get('/api/oauth/userinfo', async (req, res) => {
    const authHeader = req.headers.authorization;
    
    if (!authHeader) {
        return res.status(401).json({ error: 'Missing authorization header' });
    }

    try {
        const response = await fetch(`${OAUTH_CONFIG.authServer}/api/oauth2/userinfo`, {
            headers: {
                'Authorization': authHeader
            }
        });

        const userData = await response.json();
        
        if (response.ok) {
            res.json(userData);
        } else {
            res.status(response.status).json(userData);
        }
    } catch (error) {
        res.status(500).json({ error: 'Failed to fetch user info' });
    }
});

app.listen(3000, () => {
    console.log('Server running on port 3000');
});
```

## Best Practices für die Sicherheit

### 1. Schutz des Client-Secrets

* ✅ Empfohlen: `client_secret` auf dem Backend-Server speichern
* ❌ Vermeiden: `client_secret` im JavaScript-Code des Frontends offenlegen

### 2. Validierung des State-Parameters

```js theme={null}
// Generate random state
const state = crypto.randomBytes(16).toString('hex');
localStorage.setItem('oauth_state', state);

// Validate state
const savedState = localStorage.getItem('oauth_state');
if (receivedState !== savedState) {
    throw new Error('CSRF attack detected');
}
```

### 3. Verwendung von HTTPS

* In der Produktion muss `HTTPS` verwendet werden
* Die Callback-URI muss `HTTPS` verwenden
* Alle API-Anfragen nutzen `HTTPS`

### 4. Sichere Speicherung von Tokens

```js theme={null}
// Set token expiry time
const expiryTime = Date.now() + (tokenData.expires_in * 1000);
localStorage.setItem('oauth_token_expiry', expiryTime);

// Check if token is expired
function isTokenExpired() {
    const expiry = localStorage.getItem('oauth_token_expiry');
    return !expiry || Date.now() > parseInt(expiry);
}
```

### 5. Fehlerbehandlung

```js theme={null}
try {
    const response = await fetch('/api/oauth2/userinfo', {
        headers: { 'Authorization': `Bearer ${token}` }
    });
    
    if (!response.ok) {
        if (response.status === 401) {
            // Token expired, need to re-login
            clearToken();
            showLoginSection();
        } else {
            throw new Error(`HTTP ${response.status}`);
        }
    }
    
    const userInfo = await response.json();
    return userInfo;
} catch (error) {
    console.error('API request failed:', error);
    // Handle network errors etc.
}
```

## FAQ

### Q1: Wie erreicht man ein echtes Ein-Klick-Anmeldeerlebnis?

A: Fügen Sie der Autorisierungs-URL einfach den Parameter `auto_authorize=true` hinzu. Nach der Anmeldung des Nutzers wird die Autorisierung automatisch erteilt – ohne zusätzlichen Bestätigungsschritt:

```js theme={null}
const authUrl = 'https://your-domain.com/api/oauth2/authorize?' +
  'client_id=YOUR_CLIENT_ID&' +
  'auto_authorize=true&' + // Key parameter
  '...other parameters';
```

### Q2: Werden Tokens automatisch aktualisiert?

A: Ja, in unser SDK ist eine automatische Token-Aktualisierung integriert:

* Access Token: 2 Stunden gültig, automatische Aktualisierung 5 Minuten vor Ablauf
* Refresh Token: 30 Tage gültig, wird verwendet, um neue Access Tokens zu erhalten
* Nahtlose Aktualisierung: Alle API-Aufrufe prüfen automatisch abgelaufene Tokens und aktualisieren sie bei Bedarf
* Wiederholung bei Fehlern: Gibt die API einen 401-Fehler zurück, wird automatisch versucht, das Token zu aktualisieren und die Anfrage erneut zu senden

```js theme={null}
// The SDK will handle token refresh automatically, no manual handling needed
const userInfo = await oauthManager.fetchUserInfo(); // token auto-refresh
```

Nur wenn auch das Refresh Token abgelaufen ist, muss sich der Nutzer erneut anmelden.

### Q3: Welche Nutzerinformationen kann ich abrufen?

A: Je nach autorisiertem Scope können Sie Folgendes erhalten:

* Scope `profile`: Benutzername, E-Mail
* Scope `balance`: Informationen zum Kontostand

### Q4: Wie teste ich die OAuth-Integration?

A:

1. Verwenden Sie in der Entwicklung `HTTP localhost` zum Testen
2. Nutzen Sie unsere bereitgestellten Test-Tools, um den Autorisierungsablauf zu verifizieren
3. Prüfen Sie die Netzwerkanfragen in den Entwicklertools Ihres Browsers

### Q5: Welche Programmiersprachen werden unterstützt?

A: Unsere OAuth2-API ist standardkonform und unterstützt alle gängigen Programmiersprachen:

* JavaScript/Node.js
* Python
* PHP
* Java
* C#/.NET
* Go
* Ruby

### Q6: Wie kann ein Nutzer die Autorisierung widerrufen?

## A: Nutzer können die Autorisierung von Drittanbieter-Apps in unserem Nutzer-Center > Seite „Autorisierungsverwaltung“ widerrufen.

Zuletzt aktualisiert: 2026-06-01
