Claude Code & OAuth2: Sichere Authentifizierung und Authorization implementieren

OAuth2 gehört zu den Themen, bei denen die Spezifikation einfach klingt und die Implementierung komplex ist. PKCE, State-Parameter, Token-Rotation, Scope-Hierarchien — jedes Detail ein potenzielles Sicherheitsleck, wenn es falsch umgesetzt wird. Claude Code macht den Unterschied: nicht weil es die Spezifikation kennt, sondern weil es deinen konkreten Code liest und die Lücken zwischen Spezifikation und Implementierung sieht.

Dieser Artikel zeigt, wie du die gängigsten OAuth2-Flows mit Claude Code umsetzt — von Authorization Code mit PKCE über Social Login bis zu Passport.js und NextAuth.js. Mit konkreten Codebeispielen und den Sicherheitsaspekten, die in der Praxis oft übersehen werden.

Claude Code Mastery — Auth, Agents, Hooks auf Deutsch

OAuth2 ist eine von vielen Stärken von Claude Code. Im Kurs lernst du, wie du Authentifizierung, APIs, Agents und komplexe Workflows produktiv umsetzt — vollständig auf Deutsch, einmalig bezahlt.

Zum Kurs — Jetzt starten → Einmalzahlung · Kein Abo · 14 Tage Rückgaberecht

1. OAuth2 Flows im Überblick

OAuth2 ist kein einzelnes Protokoll, sondern ein Framework mit mehreren Flows für unterschiedliche Anwendungsfälle. Die Wahl des falschen Flows ist der häufigste Fehler in der Praxis.

Claude Code hilft dabei, den richtigen Flow zu identifizieren, bevor du anfängst zu implementieren. Eine einfache Frage wie claude "Welchen OAuth2-Flow brauche ich für eine SPA mit Node.js-Backend?" liefert eine begründete Empfehlung — inklusive Hinweis, warum der Implicit Flow keine Option mehr ist.

2. Authorization Code Flow mit PKCE

PKCE (Proof Key for Code Exchange) ist die moderne Erweiterung des Authorization Code Flow. Es löst das Problem, dass SPAs kein sicheres Client-Secret speichern können — ohne PKCE wäre der Authorization Code angreifbar.

Das Grundprinzip: Der Client generiert einen zufälligen code_verifier, hasht ihn zu einem code_challenge, und sendet den Challenge zusammen mit dem Authorization Request. Beim Token-Austausch muss der ursprüngliche Verifier mitgeschickt werden — nur der legitime Client kann das.

// PKCE Code Verifier und Challenge generieren
import crypto from 'crypto';

function generateCodeVerifier() {
  return crypto.randomBytes(32).toString('base64url');
}

function generateCodeChallenge(verifier) {
  return crypto
    .createHash('sha256')
    .update(verifier)
    .digest('base64url');
}

// Authorization URL aufbauen
function buildAuthUrl(clientId, redirectUri, scopes) {
  const verifier = generateCodeVerifier();
  const challenge = generateCodeChallenge(verifier);
  const state = crypto.randomBytes(16).toString('hex');

  // verifier und state in der Session speichern
  session.codeVerifier = verifier;
  session.oauthState = state;

  const params = new URLSearchParams({
    response_type: 'code',
    client_id: clientId,
    redirect_uri: redirectUri,
    scope: scopes.join(' '),
    state,
    code_challenge: challenge,
    code_challenge_method: 'S256',
  });

  return `https://auth.example.com/authorize?${params}`;
}

State-Parameter nicht vergessen: Der state-Parameter schützt vor CSRF-Angriffen. Generiere ihn kryptografisch zufällig, speichere ihn in der Session und verifiziere ihn im Callback — bevor du irgendetwas anderes mit dem Authorization Code machst.

Token-Austausch im Callback

// Callback-Handler: Code gegen Token tauschen
async function handleCallback(req, res) {
  const { code, state } = req.query;

  // State verifizieren
  if (state !== req.session.oauthState) {
    return res.status(400).send('State mismatch — möglicher CSRF-Angriff');
  }

  const response = await fetch('https://auth.example.com/token', {
    method: 'POST',
    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
    body: new URLSearchParams({
      grant_type: 'authorization_code',
      code,
      redirect_uri: process.env.OAUTH_REDIRECT_URI,
      client_id: process.env.OAUTH_CLIENT_ID,
      code_verifier: req.session.codeVerifier,
    }),
  });

  const tokens = await response.json();
  // Tokens sicher speichern (siehe Abschnitt 5)
}

3. Client Credentials Flow

Für Server-zu-Server-Kommunikation — ohne Benutzer, ohne Browser. Ein Backend ruft eine API auf und authentifiziert sich mit seiner eigenen Identität, nicht im Namen eines Benutzers.

// Client Credentials: Token anfordern
async function getClientCredentialsToken() {
  const credentials = Buffer.from(
    `${process.env.CLIENT_ID}:${process.env.CLIENT_SECRET}`
  ).toString('base64');

  const response = await fetch('https://auth.example.com/token', {
    method: 'POST',
    headers: {
      'Authorization': `Basic ${credentials}`,
      'Content-Type': 'application/x-www-form-urlencoded',
    },
    body: new URLSearchParams({
      grant_type: 'client_credentials',
      scope: 'api:read api:write',
    }),
  });

  return response.json();
}

Tokens cachen: Client Credentials Tokens haben eine Ablaufzeit (meistens 1 Stunde). Ein neues Token bei jedem Request anzufordern ist ineffizient. Cache das Token und erneuere es kurz vor dem Ablauf — expires_in aus der Response gibt die Dauer in Sekunden an.

4. Social Login: Google und GitHub

Social Login ist Authorization Code Flow mit einem bekannten Provider. Die Grundstruktur ist dieselbe — aber jeder Provider hat eigene Endpoints, Scopes und Besonderheiten.

Passport.js Integration

Passport.js abstrahiert die Provider-Unterschiede. Die Strategie-Konfiguration ist pro Provider unterschiedlich, der Request-Flow ist immer gleich.

import passport from 'passport';
import { Strategy as GoogleStrategy } from 'passport-google-oauth20';
import { Strategy as GitHubStrategy } from 'passport-github2';

// Google OAuth2
passport.use(new GoogleStrategy({
  clientID: process.env.GOOGLE_CLIENT_ID,
  clientSecret: process.env.GOOGLE_CLIENT_SECRET,
  callbackURL: '/auth/google/callback',
  scope: ['profile', 'email'],
}, async (accessToken, refreshToken, profile, done) => {
  const user = await findOrCreateUser({
    provider: 'google',
    providerId: profile.id,
    email: profile.emails[0].value,
    name: profile.displayName,
  });
  return done(null, user);
}));

// GitHub OAuth2
passport.use(new GitHubStrategy({
  clientID: process.env.GITHUB_CLIENT_ID,
  clientSecret: process.env.GITHUB_CLIENT_SECRET,
  callbackURL: '/auth/github/callback',
  scope: ['user:email'],
}, async (accessToken, refreshToken, profile, done) => {
  const email = profile.emails?.find(e => e.primary)?.value;
  const user = await findOrCreateUser({
    provider: 'github',
    providerId: profile.id,
    email,
    name: profile.displayName || profile.username,
  });
  return done(null, user);
}));

// Routes
app.get('/auth/google', passport.authenticate('google'));
app.get('/auth/google/callback',
  passport.authenticate('google', { failureRedirect: '/login' }),
  (req, res) => res.redirect('/dashboard')
);

NextAuth.js für Next.js-Projekte

In Next.js-Projekten ist NextAuth.js die etablierte Lösung. Die Konfiguration ist deklarativ — Provider hinzufügen, Callbacks definieren, fertig.

// pages/api/auth/[...nextauth].ts
import NextAuth from 'next-auth';
import GoogleProvider from 'next-auth/providers/google';
import GitHubProvider from 'next-auth/providers/github';

export default NextAuth({
  providers: [
    GoogleProvider({
      clientId: process.env.GOOGLE_CLIENT_ID!,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
    }),
    GitHubProvider({
      clientId: process.env.GITHUB_CLIENT_ID!,
      clientSecret: process.env.GITHUB_CLIENT_SECRET!,
    }),
  ],
  callbacks: {
    async session({ session, token }) {
      // User-ID aus Token in Session übertragen
      if (session.user) {
        session.user.id = token.sub!;
      }
      return session;
    },
    async jwt({ token, account }) {
      // Refresh Token speichern (nur beim ersten Login vorhanden)
      if (account?.refresh_token) {
        token.refreshToken = account.refresh_token;
      }
      return token;
    },
  },
  session: { strategy: 'jwt' },
});

5. Token-Speicherung: Was sicher ist und was nicht

Die häufigste Sicherheitslücke in OAuth2-Implementierungen ist nicht der Flow selbst — sondern wo Tokens gespeichert werden. Falsche Speicherung macht korrekt implementiertes PKCE wertlos.

"Der Access Token lebt im Memory der SPA — kurzlebig, nie persistent. Der Refresh Token lebt in einem HttpOnly-Cookie mit SameSite=Strict — JavaScript sieht ihn nie."

6. Refresh Token Rotation

Access Tokens haben kurze Lebenszeiten (15 Minuten bis 1 Stunde). Refresh Token Rotation erneuert bei jedem Refresh-Vorgang auch den Refresh Token — ein gestohlener Refresh Token wird damit automatisch invalidiert, sobald der echte Benutzer ihn verwendet.

// Refresh Token Rotation: neuen Access Token anfordern
async function refreshAccessToken(refreshToken) {
  const response = await fetch('https://auth.example.com/token', {
    method: 'POST',
    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
    body: new URLSearchParams({
      grant_type: 'refresh_token',
      refresh_token: refreshToken,
      client_id: process.env.OAUTH_CLIENT_ID,
    }),
  });

  if (!response.ok) {
    // Refresh Token abgelaufen oder invalidiert
    // Benutzer muss sich neu anmelden
    throw new Error('Token refresh fehlgeschlagen');
  }

  const tokens = await response.json();

  // Neuen Refresh Token in HttpOnly Cookie setzen
  // (der alte ist nach Rotation ungültig)
  res.cookie('refresh_token', tokens.refresh_token, {
    httpOnly: true,
    secure: true,
    sameSite: 'strict',
    maxAge: 30 * 24 * 60 * 60 * 1000, // 30 Tage
  });

  return tokens.access_token;
}

Rotation implementieren, nicht nur vertrauen: Nicht alle Provider implementieren Refresh Token Rotation automatisch. Prüfe in der Provider-Dokumentation, ob Rotation aktiviert werden muss — bei Auth0 ist es eine explizite Einstellung, bei Google passiert es automatisch.

7. Scopes und Permissions

Scopes definieren, welche Ressourcen ein Token freischaltet. Das Prinzip des minimalen Privilegs gilt hier besonders: nur die Scopes anfordern, die tatsächlich gebraucht werden.

Claude Code hilft beim Scope-Audit: Übergib deinen bestehenden OAuth2-Code und frage explizit nach, welche Scopes wofür verwendet werden und ob alle tatsächlich nötig sind.

claude "Analysiere meinen OAuth2-Code und zeige, welche Scopes ich anfordere,
wofür sie jeweils verwendet werden, und welche ich weglassen könnte."

Ein typischer Befund: eine App fordert email profile openid an, verwendet aber nur die E-Mail-Adresse. Das Profil-Scope ist überflüssig — und jede unnötige Permission ist eine Angriffsfläche, die man nicht braucht.

Custom Scopes im eigenen Authorization Server

Wenn du einen eigenen Auth-Server betreibst (z.B. mit node-oauth2-server oder Keycloak), definierst du die Scopes selbst. Benenne sie semantisch und nach dem Ressource-Owner-Prinzip:

// Scope-Definition: Ressource:Aktion
const SCOPES = {
  'orders:read':   'Bestellungen lesen',
  'orders:write':  'Bestellungen erstellen und ändern',
  'users:read':    'Benutzerprofile lesen',
  'users:admin':   'Benutzer verwalten (nur Admins)',
  'reports:export':'Berichte exportieren',
};

8. Mit Claude Code OAuth2-Probleme debuggen

OAuth2-Fehler sind notorisch schwer zu debuggen: invalid_grant, invalid_client, access_denied — die Fehlercodes sagen wenig. Claude Code analysiert die gesamte Request-Response-Kette und identifiziert die Ursache.

claude "Ich bekomme 'invalid_grant' beim Token-Austausch.
Hier ist mein Callback-Handler: [Code einfügen].
Hier die Request-Details: [curl-Output einfügen].
Was stimmt nicht?"

Typische Ursachen, die Claude Code findet: der Authorization Code wurde bereits verwendet (darf nur einmal eingelöst werden), die redirect_uri stimmt nicht exakt überein (ein trailing Slash macht den Unterschied), oder der Code Verifier wird nicht korrekt kodiert.


Claude Code Mastery — Auth, APIs und produktive Workflows

OAuth2 ist ein Thema im Kurs. Du lernst auch: sichere API-Implementierung, Agents, MCP-Server, Hooks und Team-Workflows — vollständig auf Deutsch, einmalig bezahlt.

Jetzt starten → Einmalzahlung · Kein Abo · 14 Tage Rückgaberecht

Kurs · Claude Code Mastery

Von OAuth2 bis zum produktiven AI-Agenten

Auth. APIs. Agents. MCP. Hooks. Multi-Agent-Workflows. Alles auf Deutsch, einmalig bezahlt — kein Abo, keine Plattformabhängigkeit.

Jetzt einsteigen → Kursübersicht ansehen →

Einmalzahlung · Kein Abo · 14 Tage Rückgaberecht