OAuth 2.0 regelt die Autorisierung (wer darf was), OpenID Connect setzt die Authentifizierung (wer bist du) obendrauf. Zusammen bilden sie das RĂŒckgrat moderner Login-Systeme wie "Anmelden mit Google". In diesem Artikel zeige ich dir, wie das alles zusammenhĂ€ngt, welche Grant Types es gibt, was JWTs sind und wie du das Ganze in TypeScript/NestJS umsetzt.

đŸ€Ż Warum Authentifizierung so schwer ist

Hand aufs Herz: Hast du schon mal daran gedacht, Authentifizierung selbst zu bauen? Passwörter hashen, Sessions verwalten, Token generieren, Refresh-Logik implementieren, Brute-Force-Schutz, Rate Limiting, Password Reset Flows... Die Liste hört nicht auf.

Das Problem ist nicht, dass es technisch unmöglich wĂ€re. Das Problem ist, dass du bei einem einzigen Fehler die TĂŒr fĂŒr Angreifer öffnest. Und genau deshalb gibt es Standards wie OAuth 2.0 und OpenID Connect. Sie nehmen dir die schwierigsten Teile ab, damit du dich auf deine eigentliche App konzentrieren kannst.

🔑 OAuth 2.0: Autorisierung, nicht Authentifizierung!

Das ist der hÀufigste Irrtum: OAuth 2.0 ist kein Authentifizierungsprotokoll. Es ist ein Autorisierungs-Framework. Der Unterschied?

  • Authentifizierung = "Wer bist du?" (IdentitĂ€t prĂŒfen)
  • Autorisierung = "Was darfst du?" (Berechtigungen prĂŒfen)

OAuth 2.0 löst folgendes Problem: Wie kann eine App auf Ressourcen eines Benutzers zugreifen, ohne dass der Benutzer sein Passwort teilen muss? Stell dir vor, eine Kalender-App will auf deine Google-Kontakte zugreifen. Statt dein Google-Passwort einzugeben, autorisierst du die App ĂŒber einen kontrollierten Flow. Genial, oder?

đŸ—ïž Die vier Rollen in OAuth 2.0

OAuth 2.0 definiert vier zentrale Rollen:

RolleBeschreibungBeispiel
Resource OwnerDer Benutzer, dem die Daten gehörenDu selbst
ClientDie App, die auf Daten zugreifen willDeine Kalender-App
Authorization ServerVergibt Tokens nach erfolgreicher AutorisierungGoogle OAuth Server
Resource ServerHĂ€lt die geschĂŒtzten RessourcenGoogle Contacts API

Diese vier Rollen interagieren in einem definierten Ablauf, den sogenannten Grant Types.

🔄 Grant Types: Der richtige Flow fĂŒr den richtigen Anwendungsfall

Authorization Code Grant (+ PKCE), Der Goldstandard

Das ist der Grant Type, den du in 90% der FĂ€lle verwenden solltest. Er funktioniert so:

  1. Deine App leitet den Benutzer zum Authorization Server weiter
  2. Der Benutzer loggt sich ein und gibt seine Zustimmung
  3. Der Authorization Server leitet zurĂŒck mit einem Authorization Code
  4. Deine App tauscht den Code im Backend gegen ein Access Token

PKCE (Proof Key for Code Exchange, gesprochen "Pixie") fĂŒgt eine zusĂ€tzliche Sicherheitsebene hinzu. Deine App generiert einen zufĂ€lligen code_verifier und schickt dessen Hash (code_challenge) mit dem initialen Request. Beim Token-Austausch muss der originale code_verifier mitgeschickt werden. Das verhindert, dass ein Angreifer den Authorization Code abfangen und nutzen kann.

// PKCE: code_verifier und code_challenge generieren
import * as crypto from 'crypto';

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

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

const codeVerifier = generateCodeVerifier();
const codeChallenge = generateCodeChallenge(codeVerifier);

// Im Authorization Request:
// ?response_type=code&code_challenge={codeChallenge}&code_challenge_method=S256

Client Credentials Grant, Maschine-zu-Maschine

Kein Benutzer involviert. Dein Backend-Service authentifiziert sich direkt beim Authorization Server mit seiner Client ID und seinem Client Secret. Perfekt fĂŒr Microservice-Kommunikation.

# Client Credentials Grant
curl -X POST https://auth.example.com/oauth/token   -d "grant_type=client_credentials"   -d "client_id=YOUR_CLIENT_ID"   -d "client_secret=YOUR_CLIENT_SECRET"   -d "scope=read:users"

Device Code Grant, FĂŒr GerĂ€te ohne Browser

Smart TVs, CLI-Tools, IoT-GerĂ€te, ĂŒberall dort, wo man keinen Browser hat oder nur schwer tippen kann. Das GerĂ€t zeigt einen Code an, du öffnest eine URL auf deinem Handy, gibst den Code ein und autorisierst. Kennst du bestimmt von Netflix oder GitHub CLI.

⚠ Deprecated: Implicit Grant & Password Grant

Der Implicit Grant hat das Token direkt in der URL zurĂŒckgegeben. Problem: URLs landen in Browser-History, Logs und Referer-Headern. Sicherheitstechnisch ein Albtraum.

Der Resource Owner Password Credentials Grant hat den Benutzernamen und das Passwort direkt an die App ĂŒbergeben. Das widerspricht dem gesamten Zweck von OAuth. Beide sind in der aktuellen Best Practice als veraltet markiert.

OAuth 2.0
OAuth 2.0 is the industry-standard protocol for authorization.

đŸȘȘ OpenID Connect: Authentifizierung on top

Okay, OAuth 2.0 sagt uns, was jemand darf. Aber wer ist dieser Jemand? Genau hier kommt OpenID Connect (OIDC) ins Spiel. Es ist eine dĂŒnne Schicht auf OAuth 2.0, die Authentifizierung hinzufĂŒgt.

Der entscheidende Unterschied: OIDC fĂŒhrt den ID Token ein. WĂ€hrend ein Access Token sagt "dieser Token hat Zugriff auf X", sagt ein ID Token "dieser Token gehört zu Benutzer Y mit E-Mail Z".

How OpenID Connect Works
OpenID Connect explained for developers.

đŸŽ« ID Token vs Access Token vs Refresh Token

Drei Tokens, drei Aufgaben:

TokenZweckEmpfÀngerLebensdauer
ID TokenIdentitÀt des Benutzers (Authentifizierung)Deine App (Client)Kurz (Minuten)
Access TokenZugriff auf geschĂŒtzte Ressourcen (Autorisierung)Resource Server (API)Kurz (Minuten bis Stunden)
Refresh TokenNeues Access Token holen, ohne erneuten LoginAuthorization ServerLang (Tage bis Wochen)

Wichtig: Schick niemals ein ID Token an eine API! Das ID Token ist fĂŒr deine App, um den Benutzer zu identifizieren. FĂŒr API-Zugriffe nutzt du das Access Token.

🧬 JWT: Anatomie eines Tokens

Die meisten ID Tokens (und viele Access Tokens) sind JWTs (JSON Web Tokens). Ein JWT besteht aus drei Teilen, getrennt durch Punkte:

eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.signature
# Header               . Payload                     . Signature

Jeder Teil ist Base64url-encoded:

// Header — Algorithmus und Token-Typ
{
  "alg": "RS256",
  "typ": "JWT",
  "kid": "my-key-id"    // Key ID zur SignaturprĂŒfung
}

// Payload — Die Claims (Behauptungen)
{
  "iss": "https://auth.example.com",  // Issuer
  "sub": "user-123",                   // Subject (Benutzer-ID)
  "aud": "my-app",                     // Audience (fĂŒr wen)
  "exp": 1700000000,                   // Expiration
  "iat": 1699999000,                   // Issued At
  "email": "[email protected]",         // OIDC Claim
  "name": "Max Mustermann"             // OIDC Claim
}

// Signature — HMAC oder RSA-Signatur ĂŒber Header + Payload
// Wird mit dem Private Key des Authorization Servers erzeugt
// Kann mit dem Public Key verifiziert werden

Die Signatur stellt sicher, dass niemand den Inhalt manipuliert hat. Deine App kann den Public Key des Authorization Servers (via JWKS-Endpoint) abrufen und die Signatur prĂŒfen, ohne den Authorization Server kontaktieren zu mĂŒssen.

🚀 Praxisbeispiel: "Login mit Google/GitHub"

So lÀuft ein typischer "Login mit Google"-Flow ab:

  1. Benutzer klickt "Mit Google anmelden", Deine App leitet zum Google Authorization Endpoint weiter
  2. Google zeigt Login-Seite, Benutzer gibt Credentials ein
  3. Consent Screen, "App XY möchte auf dein Profil und deine E-Mail zugreifen"
  4. Redirect zurĂŒck, Mit Authorization Code in der URL
  5. Token-Austausch, Dein Backend tauscht den Code gegen ID Token + Access Token
  6. ID Token auswerten, Benutzer identifizieren, Session erstellen

🎯 Scopes und Claims

Scopes definieren, welche Berechtigungen angefragt werden. OIDC definiert Standard-Scopes:

  • openid, Pflicht fĂŒr OIDC, liefert den ID Token
  • profile, Name, Bild, etc.
  • email, E-Mail-Adresse
  • offline_access, Refresh Token anfordern

Claims sind die einzelnen Datenpunkte im Token, die durch Scopes freigeschaltet werden. Der Scope email schaltet z.B. die Claims email und email_verified frei.

REST-APIs verstehen und nutzen
Alles was du ĂŒber REST-APIs wissen musst.

đŸ› ïž Implementation mit NestJS

Jetzt wird's praktisch. So setzt du JWT-basierte Authentifizierung mit NestJS um:

# Dependencies installieren
npm install @nestjs/jwt @nestjs/passport passport passport-jwt
npm install -D @types/passport-jwt
// auth/jwt.strategy.ts
import { Injectable } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { ExtractJwt, Strategy } from 'passport-jwt';
import { passportJwtSecret } from 'jwks-rsa';

@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
  constructor() {
    super({
      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
      // JWKS-Endpoint deines OIDC Providers
      secretOrKeyProvider: passportJwtSecret({
        cache: true,
        rateLimit: true,
        jwksRequestsPerMinute: 5,
        jwksUri: 'https://auth.example.com/.well-known/jwks.json',
      }),
      issuer: 'https://auth.example.com/',
      audience: 'my-api',
      algorithms: ['RS256'],
    });
  }

  async validate(payload: any) {
    return {
      userId: payload.sub,
      email: payload.email,
      roles: payload.roles || [],
    };
  }
}
// auth/auth.module.ts
import { Module } from '@nestjs/common';
import { PassportModule } from '@nestjs/passport';
import { JwtStrategy } from './jwt.strategy';

@Module({
  imports: [PassportModule.register({ defaultStrategy: 'jwt' })],
  providers: [JwtStrategy],
  exports: [PassportModule],
})
export class AuthModule {}
// GeschĂŒtzte Route
import { Controller, Get, UseGuards } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';

@Controller('profile')
export class ProfileController {
  @Get()
  @UseGuards(AuthGuard('jwt'))
  getProfile(@Req() req) {
    // req.user enthÀlt das validierte Token-Payload
    return {
      userId: req.user.userId,
      email: req.user.email,
    };
  }
}

⚠ HĂ€ufige Fehler und Sicherheitsfallen

Hier die hÀufigsten Stolperfallen, die ich immer wieder sehe:

1. Tokens im localStorage speichern

Problem: Jedes XSS-Script kann den localStorage auslesen. Speichere Access Tokens stattdessen in httpOnly Cookies oder halte sie nur im Speicher (In-Memory).

2. Redirect URI nicht validieren

Problem: Ohne strenge Validierung der Redirect URI kann ein Angreifer den Authorization Code an seine eigene URL umleiten. Registriere exakte Redirect URIs, keine Wildcards.

3. CSRF-Schutz vergessen

Problem: Ohne state-Parameter kann ein Angreifer einen CSRF-Angriff durchfĂŒhren. Generiere immer einen zufĂ€lligen state-Wert und prĂŒfe ihn beim Callback.

4. Token-Signatur nicht prĂŒfen

Problem: Wenn du den Token nur dekodierst, aber die Signatur nicht prĂŒfst, kann jeder beliebige Tokens erstellen. Immer die Signatur gegen den JWKS-Endpoint verifizieren.

5. Zu lange Token-Lebensdauer

Problem: Access Tokens sollten kurzlebig sein (5-15 Minuten). Nutze Refresh Tokens fĂŒr lĂ€ngere Sessions.

CORS: Cross-Origin Resource Sharing erklÀrt
Alles ĂŒber CORS und warum es so wichtig fĂŒr die Sicherheit deiner APIs ist.
DKIM, DMARC und SPF: Der Schutzschild deiner E-Mail-Kommunikation
Wie du deine E-Mail-Sicherheit mit DKIM, DMARC und SPF absicherst.

🏠 Self-Hosting: Auth0, Keycloak & Authentik

Du musst nicht zwingend einen Cloud-Provider nutzen. Es gibt großartige Self-Hosting-Optionen:

  • Keycloak, Der Klassiker. Java-basiert, extrem flexibel, riesige Community. UnterstĂŒtzt SAML, OIDC, LDAP und mehr. Kann allerdings ein Ressourcen-Fresser sein.
  • Authentik, Modern, Python-basiert, schicke UI. Leichtgewichtiger als Keycloak und einfacher zu konfigurieren. Mein persönlicher Favorit fĂŒr neue Projekte.
  • Auth0, Eigentlich ein Cloud-Service, aber mit einem großzĂŒgigen Free Tier. Wenn du keine Lust auf Self-Hosting hast, ist Auth0 eine solide Wahl.
# Authentik mit Docker Compose starten
# docker-compose.yml
version: "3.4"
services:
  authentik-server:
    image: ghcr.io/goauthentik/server:latest
    command: server
    environment:
      AUTHENTIK_SECRET_KEY: "generate-a-long-random-string"
      AUTHENTIK_REDIS__HOST: redis
      AUTHENTIK_POSTGRESQL__HOST: postgresql
      AUTHENTIK_POSTGRESQL__USER: authentik
      AUTHENTIK_POSTGRESQL__PASSWORD: authentik
      AUTHENTIK_POSTGRESQL__NAME: authentik
    ports:
      - "9000:9000"
      - "9443:9443"

💡 Fazit

OAuth 2.0 und OpenID Connect sind keine Raketenwissenschaft, aber sie haben viele bewegliche Teile. Die wichtigsten Takeaways:

  • OAuth 2.0 = Autorisierung, OpenID Connect = Authentifizierung obendrauf
  • Nutze Authorization Code + PKCE als Standard-Flow
  • Vergiss Implicit und Password Grant, die sind veraltet
  • Verstehe den Unterschied zwischen ID Token, Access Token und Refresh Token
  • PrĂŒfe immer die Token-Signatur
  • Speichere Tokens niemals im localStorage
  • Nutze bestehende Lösungen wie Keycloak oder Authentik statt selbst zu bauen

Authentifizierung selbst zu bauen ist wie sein eigenes Krypto-Protokoll zu schreiben: Technisch machbar, aber fast immer eine schlechte Idee. Steh auf den Schultern von Giganten.