Übungsaufgaben zur Anmeldung mit Keycloak in Vue: Mitfahrgesuche mit Token

https://bildung.social/@oerinformatik/

https://oer-informatik.de/vue-keycloak-oidc-uebungsaufgaben

Vue 3, Keycloak, OpenID Connect, OAuth 2.0, PKCE, Access Token, Navigation Guard, oidc-client-ts, Vitest, Übungsaufgaben, Prüfungsvorbereitung

tl/dr; (Bearbeitungszeit insgesamt 53 min, mehrere Aufgaben): Die Mitfahrgesuche in der Web-App des Mitfahrlabors sollen nur für angemeldete Personen erreichbar sein. In diesen Übungen bringst Du die Schritte des Authorization Code Flows mit PKCE in die richtige Reihenfolge und ordnest Tokens ihren Empfängern zu. Danach schützt Du die Seite „Meine Gesuche“ mit dem Navigation Guard und schickst ein neues Gesuch mit Access Token an die API, jeweils mit Tests. Zum Schluss findest Du anhand von Fehlermeldungen heraus, was bei der Anmeldung eines anderen Paars schiefläuft, und nimmst Stellung zu zwei Vorschlägen für mehr Bequemlichkeit [AI-Assisted]. (Zuletzt geändert am 11.11.2026)

Dieser Artikel ist Teil einer Artikelreihe über die Web-App mit Vue 3 und TypeScript: Projektübersicht / XP-Projektstart / TypeScript für Java-Entwickler*innen / Komponenten testgetrieben / API-Client aus OpenAPI / Router und Continuous Integration / Formulare und Validierung / Refactoring mit Pinia / Anmeldung mit Keycloak / Fahrer-Sicht und Coding Standards / Build und Auslieferung / End-to-End-Tests mit Playwright / Change Request Live-Aktualisierung

Überblick: Die Vue-App im Browser mit Navigation Guard, Store und oidc-client-ts holt bei Keycloak mit Authorization Code und PKCE die Tokens und ruft das Backend mit Authorization: Bearer auf (mit KI erstellt)

Diese Übungsaufgaben gehören zum Artikel Anmeldung mit Keycloak in Vue: Authorization Code mit PKCE. Alle Aufgaben spielen im Projektteam der Mitfahr-App Mitfahrlabor. Ausgangspunkt für die Aufgaben 2.1 und 2.2 ist das Projekt mitfahrlabor-web im Ordner src/08-vue-keycloak-oidc des Repositorys dieser Reihe mit dem Store useSitzungStore, dem Navigation Guard und der Anmelde-Attrappe src/mocks/anmeldung.ts. Was für die Gesuche fehlt, steht vollständig in den Aufgaben; die Lösungen der Übungen zu den vorigen Artikeln brauchst Du nicht.

Für die Gesuche nimmt das Team einen Endpunkt an, den das Backend in einer späteren Version bekommen soll: POST /v1/gesuche legt ein Gesuch für die angemeldete Person an (Rolle NUTZER), antwortet mit 201 und dem Gesuch samt id und mitfahrerId, ohne Token mit 401.

Hinweise zur Bearbeitung: Bis zur Teil-1-Prüfung der IT-Berufe solltest Du die Basiskompetenz beherrschen, also Aufgabe 1.1. Als FIA, FIDP oder FIDV musst Du zur Teil-2-Prüfung Aufgaben auf Kernkompetenz-Ebene lösen können, das sind Aufgabe 2.1 und Aufgabe 2.2. Von FIA wird zusätzlich erwartet, dass ihnen Aufgaben auf Profilkompetenz-Ebene (Aufgabe 3.1) keine Probleme bereiten. Die Operatoren (ordnen, zuordnen, vorhersagen, entwickeln, analysieren, Stellung nehmen …) zeigen, wie ausführlich Du antworten sollst. Code schreibst Du in TypeScript bzw. als Single-File-Component von Vue 3 und prüfst ihn mit npm test, npm run type-check und npm run lint. Schreib Deine Lösung zuerst vollständig auf, bevor Du sie mit der Beispiellösung vergleichst.

Du möchtest eigene Aufgaben zum Üben von einem KI-Sprachmodell generieren lassen?

Kopiere den Text in ein KI-Sprachmodell Deiner Wahl und passe Kompetenzstufe, Anzahl, Bearbeitungszeit und Szenario bei Bedarf an. Wie die Aufgaben aufgebaut sind, beschreibt der Artikel Wie sind die Aufgaben aufgebaut?. Prüfe die Ergebnisse mit dem Lehrartikel: KI-Sprachmodelle machen Fehler.

Erstelle neue Übungsaufgaben zum Thema „Anmeldung mit Keycloak in Vue:
Mitfahrgesuche mit Token“.

Bauplan: Halte Dich an Aufbau, Kompetenzstufen, Operatoren,
Anforderungsbereiche, Punkte und Zeitschätzung, die im Artikel
https://oer-informatik.de/aufgaben-aufbau beschrieben sind.

Fachliche Grundlage:
- https://oer-informatik.de/vue-keycloak-oidc
- https://oer-informatik.de/szenario-mitfahrlabor
- https://oer-informatik.de/flurmonitor-change-request-keycloak
- Stilbeispiel: https://oer-informatik.de/vue-keycloak-oidc-uebungsaufgaben

Die Aufgaben sollen Inhalte zu diesen Schlagwörtern abdecken:
- Vue 3
- Keycloak
- OpenID Connect
- OAuth 2.0
- PKCE
- Access Token
- Navigation Guard
- oidc-client-ts
- Vitest

Kompetenzstufe: Kernkompetenz (alternativ: Basiskompetenz, Profilkompetenz, Expertise)
Anzahl: 1 Aufgabe mit Teilaufgaben
Bearbeitungszeit in Minuten ca.: <5 bis 30 min>
Szenario: eines der Szenarien aus dem Bauplan

Vorgehen:
1. Lies zuerst die verlinkten Artikel. Kannst Du keine Webseiten
   abrufen, sag mir das, statt zu raten.
2. Steht bei einer Angabe noch ein Platzhalter in <…>, frag mich
   zuerst danach. Richte Punkte und Zahl der Teilaufgaben nach der
   Bearbeitungszeit (ca. 0,9 min je Punkt).
3. Gib je Aufgabe Titel mit Kompetenzstufe, Ausgangssituation und
   Teilaufgaben mit Operator und Punktzahl aus, dazu die geschätzte
   Bearbeitungszeit.
4. Gib die Beispiellösungen mit Punkteverteilung erst aus, wenn ich
   danach frage.

Aufgabe 1.1: Wer schickt was an wen? (Basiskompetenz)

(ca. 12 min Bearbeitungszeit)

Ben (Pseudonym Funkenflug) öffnet zum ersten Mal an diesem Tag die Adresse http://localhost:5173/gesuche. Die App ist wie im Artikel gebaut.

a) Bring die folgenden Schritte in die Reihenfolge, in der sie ablaufen. (6 Punkte)

Schritte der Anmeldung, Reihenfolge eintragen
Schritt Nr.
A: Keycloak leitet zu /anmeldung/rueckkehr?code=…&state=… zurück.
B: Die App schickt GET /v1/gesuche mit Authorization: Bearer … an das Backend.
C: Der Navigation Guard stellt fest, dass niemand angemeldet ist.
D: Die App schickt code und code_verifier an den Token-Endpunkt von Keycloak.
E: Die Bibliothek erzeugt einen code_verifier und leitet mit der code_challenge zu Keycloak.
F: Ben gibt auf der Anmeldeseite von Keycloak Benutzername und Passwort ein.
G: Keycloak vergleicht den Hashwert des code_verifier mit der gespeicherten Challenge und schickt die Tokens.
H: Die Rückkehr-Seite leitet mit router.replace() auf /gesuche weiter.

Reihenfolge: C, E, F, A, D, G, H, B.

Bewertung: 6 Punkte bei vollständig richtiger Reihenfolge; je falsch eingeordnetem Schritt 1 Punkt Abzug. H und B dürfen getauscht sein, wenn begründet wird, dass die Seite erst nach dem Wechsel lädt (dann 6 Punkte).

b) Ordne jedem Token zu, wer es auswertet und wozu. (3 Punkte)

Tokens nach der Anmeldung
Token Wer wertet es aus? Wozu?
ID Token
Access Token
Refresh Token
Tokens nach der Anmeldung (Lösung)
Token Wer wertet es aus? Wozu?
ID Token die Web-App (Client) erfährt, wer angemeldet ist (Pseudonym, Nutzer-ID)
Access Token das Backend (Resource Server) prüft bei jeder Anfrage, ob sie erlaubt ist
Refresh Token Keycloak (Authorization Server) stellt damit ein neues Access Token aus, ohne neue Anmeldung

Je Zeile 1 Punkt, wenn Empfänger und Zweck stimmen.

c) Ein Mitschüler schlägt vor, der Web-App ein Client Secret zu geben, „damit nicht jeder Codes eintauschen kann“. Erkläre, warum das nicht hilft und was stattdessen dafür sorgt. (2 Punkte)

Der Code der Web-App wird vollständig an den Browser ausgeliefert; ein Secret darin kann jede Person in den Entwicklerwerkzeugen lesen (1 Punkt). Die App ist ein öffentlicher Client; dass nur sie den Code eintauschen kann, sichert PKCE: Nur sie kennt den code_verifier zu dieser Anmeldung (1 Punkt).

d) Sag voraus, was passiert, wenn Ben nach der Anmeldung den Button „Abmelden“ klickt und danach im selben Tab wieder /gesuche aufruft. Nenne zwei Dinge, die nacheinander geschehen. (2 Punkte)

Je 1 Punkt:

  • Beim Abmelden löscht die App die Tokens und leitet zu Keycloak, das die Sitzung beendet und zur Startseite zurückleitet.

  • Beim Aufruf von /gesuche bricht der Guard ab und leitet wieder zur Anmeldeseite von Keycloak; Ben muss das Passwort erneut eingeben, weil auch die Sitzung bei Keycloak beendet ist.

Aufgabe 2.1: „Meine Gesuche“ nur mit Anmeldung (Kernkompetenz)

(ca. 11 min Bearbeitungszeit)

Die App bekommt zwei neue Seiten: src/views/GesucheSeite.vue für die eigenen Gesuche und src/views/HilfeSeite.vue mit einer kurzen Erklärung, die auch ohne Konto lesbar sein soll. Für die Aufgabe genügen diese Platzhalter:

<!-- src/views/GesucheSeite.vue -->
<template>
  <section>
    <h2>Meine Gesuche</h2>
  </section>
</template>

<!-- src/views/HilfeSeite.vue -->
<template>
  <section>
    <h2>So funktioniert das Mitfahrlabor</h2>
    <p>Fahrten suchen, Platz anfragen, mitfahren. Zum Anmelden brauchst Du Dein Schulkonto.</p>
  </section>
</template>

a) Ergänze in src/router/index.ts die Routen /gesuche (Name gesuche) und /so-gehts (Name hilfe). Nur angemeldete Personen dürfen die Gesuche sehen, die Hilfe ist öffentlich. Beide Seiten sollen erst bei Bedarf geladen werden. (4 Punkte)

  // vor der Route „nicht gefunden“ in routes:
  { path: '/gesuche', name: 'gesuche', component: () => import('@/views/GesucheSeite.vue') },
  {
    path: '/so-gehts',
    name: 'hilfe',
    component: () => import('@/views/HilfeSeite.vue'),
    meta: { oeffentlich: true },
  },

Bewertung: je Route 1 Punkt (Pfad, Name, dynamischer Import); 1 Punkt dafür, dass /gesuche kein meta braucht, weil der Guard alles schützt, was nicht öffentlich ist; 1 Punkt für meta: { oeffentlich: true } an der Hilfe. Ein eigener beforeEnter-Guard an /gesuche ist richtig, aber unnötig (3 Punkte).

b) Entwickle in src/__tests__/gesucheRoute.spec.ts drei Tests mit angemeldetesPinia() aus src/mocks/anmeldung.ts: Ohne Anmeldung führt /gesuche zur Anmeldung mit dem richtigen Ziel und nicht zur Seite. Ohne Anmeldung zeigt /so-gehts die Hilfe, ohne dass eine Anmeldung startet. Mit Kaffeetasse zeigt /gesuche die Überschrift „Meine Gesuche“. (6 Punkte)

import { describe, it, expect } from 'vitest'
import { mount } from '@vue/test-utils'
import { createMemoryHistory } from 'vue-router'
import App from '@/App.vue'
import { erzeugeRouter } from '@/router'
import { angemeldetesPinia } from '@/mocks/anmeldung'

describe('Routen für Gesuche und Hilfe', () => {
  it('schickt ohne Anmeldung von /gesuche zu Keycloak und merkt sich das Ziel', async () => {
    const { attrappe } = angemeldetesPinia(null)
    const router = erzeugeRouter(createMemoryHistory())
    await router.push('/gesuche')
    expect(attrappe.gestartetMitZiel).toEqual(['/gesuche'])
    expect(router.currentRoute.value.name).not.toBe('gesuche')
  })

  it('zeigt /so-gehts ohne Anmeldung und ohne Weiterleitung', async () => {
    const { pinia, attrappe } = angemeldetesPinia(null)
    const router = erzeugeRouter(createMemoryHistory())
    await router.push('/so-gehts')
    const wrapper = mount(App, { global: { plugins: [pinia, router] } })
    expect(attrappe.gestartetMitZiel).toEqual([])
    expect(wrapper.get('h2').text()).toBe('So funktioniert das Mitfahrlabor')
  })

  it('zeigt /gesuche, wenn Kaffeetasse angemeldet ist', async () => {
    const { pinia } = angemeldetesPinia()
    const router = erzeugeRouter(createMemoryHistory())
    await router.push('/gesuche')
    const wrapper = mount(App, { global: { plugins: [pinia, router] } })
    expect(router.currentRoute.value.name).toBe('gesuche')
    expect(wrapper.get('h2').text()).toBe('Meine Gesuche')
  })
})

Je Test 2 Punkte: 1 Punkt für den richtigen Aufbau (frisches Pinia mit bzw. ohne Anmeldung, Router mit Verlauf im Speicher, await router.push), 1 Punkt für die passenden Erwartungen. Ein Test der Hilfe ohne mount, der nur die Route prüft, ist richtig (2 Punkte). Die drei Tests laufen zusammen mit den 94 Tests des Code-Stands grün.

c) Begründe, warum das Team den Guard so gebaut hat, dass jede neue Route ohne weiteres Zutun geschützt ist. (2 Punkte)

Wer eine Route ergänzt und nichts einträgt, macht sie im schlimmsten Fall versehentlich unerreichbar, aber nie versehentlich öffentlich (1 Punkt). Bei einzelnen Guards je Route könnte man einen vergessen; dann wären Daten ohne Anmeldung sichtbar, und das Backend würde sie zwar nicht liefern, die Seite aber Fehler zeigen (1 Punkt). Stichwort: sichere Voreinstellung.

Aufgabe 2.2: Ein Gesuch mit Token aufgeben (Kernkompetenz)

(ca. 13 min Bearbeitungszeit)

Für POST /v1/gesuche gibt es noch keine Operation im erzeugten Client. Bis der Vertrag sie enthält, schreibt das Paar eine eigene Funktion mit fetch. Vorgegeben ist der Typ in src/domain/gesuchaufgeben.ts:

import type { Richtung } from './typen'

/** neues Mitfahrgesuch, wie es POST /v1/gesuche erwartet */
export interface GesuchNeu {
  haltepunktId: number
  richtung: Richtung
  datum: string // JJJJ-MM-TT
  zeitVon: string // HH:MM
  zeitBis: string // HH:MM
}

export type GesuchErgebnis =
  | { art: 'angelegt'; id: number }
  | { art: 'nicht-angemeldet' }
  | { art: 'abgelehnt'; status: number }

Beispiel für einen Test: Kaffeetasse (ID 3) sucht für Donnerstag, den 26.11.2026, ab Halle (Haltepunkt 3) eine Fahrt zur Schule zwischen 07:00 und 07:30 Uhr.

a) Entwickle die Funktion gibGesuchAuf(neu: GesuchNeu, holeToken: () => string | undefined, basis = '/v1'): Promise<GesuchErgebnis>. Sie schickt das Gesuch mit dem aktuellen Access Token, wertet 201, 401 und alle anderen Statuscodes aus und schickt ohne Token gar keine Anfrage. (6 Punkte)

export async function gibGesuchAuf(
  neu: GesuchNeu,
  holeToken: () => string | undefined,
  basis = '/v1',
): Promise<GesuchErgebnis> {
  const token = holeToken()
  if (token === undefined) {
    return { art: 'nicht-angemeldet' }
  }
  const antwort = await fetch(`${basis}/gesuche`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}` },
    body: JSON.stringify(neu),
  })
  if (antwort.status === 201) {
    const gesuch = (await antwort.json()) as { id: number }
    return { art: 'angelegt', id: gesuch.id }
  }
  if (antwort.status === 401) {
    return { art: 'nicht-angemeldet' }
  }
  return { art: 'abgelehnt', status: antwort.status }
}

Bewertung: 1 Punkt Token über die Funktion holeToken() erst beim Aufruf holen (nicht beim Laden des Moduls), 1 Punkt ohne Token keine Anfrage, 2 Punkte Header Authorization: Bearer <Token> und Content-Type, 2 Punkte Auswertung von 201, 401 und Rest. In der App ruft man sie mit () => sitzung.accessToken auf.

b) Entwickle drei Tests mit MSW: Header und Body der Anfrage für das Beispielgesuch, Antwort 401 und ein Aufruf ohne Token, Antwort 422. (6 Punkte)

import { describe, it, expect } from 'vitest'
import { http, HttpResponse } from 'msw'
import { gibGesuchAuf, type GesuchNeu } from '@/domain/gesuchaufgeben'
import { API } from '@/mocks/handlers'
import { server } from '@/mocks/server'
import { testToken } from '@/mocks/anmeldung'

const gesuch: GesuchNeu = { haltepunktId: 3, richtung: 'zur-schule', datum: '2026-11-26', zeitVon: '07:00', zeitBis: '07:30' }

describe('gibGesuchAuf', () => {
  it('schickt das Token im Header und keine mitfahrerId im Body', async () => {
    let kopf: string | null = null
    let body: Record<string, unknown> = {}
    server.use(
      http.post(`${API}/gesuche`, async ({ request }) => {
        kopf = request.headers.get('Authorization')
        body = (await request.json()) as Record<string, unknown>
        return HttpResponse.json({ ...body, id: 6, mitfahrerId: 3, status: 'offen' }, { status: 201 })
      }),
    )
    expect(await gibGesuchAuf(gesuch, () => testToken(3), API)).toEqual({ art: 'angelegt', id: 6 })
    expect(kopf).toBe('Bearer test-token-3')
    expect(body).not.toHaveProperty('mitfahrerId')
  })

  it('meldet „nicht angemeldet“ bei 401 und schickt ohne Token gar nichts', async () => {
    let aufrufe = 0
    server.use(
      http.post(`${API}/gesuche`, () => {
        aufrufe++
        return new HttpResponse(null, { status: 401 })
      }),
    )
    expect(await gibGesuchAuf(gesuch, () => 'abgelaufen', API)).toEqual({ art: 'nicht-angemeldet' })
    expect(await gibGesuchAuf(gesuch, () => undefined, API)).toEqual({ art: 'nicht-angemeldet' })
    expect(aufrufe).toBe(1)
  })

  it('meldet andere Ablehnungen mit Status', async () => {
    server.use(http.post(`${API}/gesuche`, () => new HttpResponse(null, { status: 422 })))
    expect(await gibGesuchAuf(gesuch, () => testToken(3), API)).toEqual({ art: 'abgelehnt', status: 422 })
  })
})

Je Test 2 Punkte. Weil das nachgebildete Backend POST /gesuche nicht kennt und unbekannte Anfragen als Fehler meldet, legt jeder Test seinen Handler mit server.use() selbst an. Der Zähler aufrufe zeigt, dass ohne Token keine Anfrage hinausgeht.

c) Erläutere, warum GesuchNeu keine mitfahrerId enthält, obwohl das Backend sie im Ergebnis zurückgibt. (2 Punkte)

Das Backend ermittelt die Person aus dem geprüften Access Token (1 Punkt). Stünde die ID im Body, könnte eine angemeldete Person eine fremde ID eintragen und Gesuche im Namen anderer aufgeben; deshalb hat der Vertrag 2.0.0 die IDs aus allen Anfragen entfernt (1 Punkt).

Aufgabe 3.1: Anmeldung mit Hindernissen (Profilkompetenz)

(ca. 17 min Bearbeitungszeit)

Ein zweites Paar im Team des Projekts Mitfahrlabor hat die Anmeldung für die Gesuche in einem eigenen Zweig gebaut, mit oidc-client-ts und demselben Realm. Beim Ausprobieren notiert es vier Beobachtungen. Ausschnitte aus seinem Code:

// src/auth/oidcAnmeldedienst.ts (Ausschnitt)
const manager = new UserManager({
  authority: 'http://localhost:8180/realms/mitfahrlabor',
  client_id: 'mitfahrlabor-web',
  redirect_uri: 'http://localhost:5173/anmeldung/rueckkehr',
  response_type: 'code',
  scope: 'openid',
  automaticSilentRenew: true,
})

// src/main.ts (Ausschnitt)
await sitzung.pruefe()
client.setConfig({ baseUrl: '/v1', auth: sitzung.accessToken })
  • Beobachtung A: Ein zweiter Entwicklungsserver lief noch. Vite startet deshalb auf Port 5174. Ruft Lina (Pseudonym Rotkehlchen) http://localhost:5174/gesuche auf, zeigt Keycloak statt der Anmeldeseite: „Invalid parameter: redirect_uri“.

  • Beobachtung B: Jonas ruft die App über http://127.0.0.1:5173/ auf. Er kommt zur Anmeldeseite und meldet sich an. Danach steht dort „Die Anmeldung hat nicht geklappt.“, die Konsole zeigt No matching state found in storage.

  • Beobachtung C: Nach der Anmeldung funktioniert alles. Nach gut fünf Minuten erscheint bei jeder Liste „Die Gesuche können gerade nicht geladen werden.“, obwohl oben noch „Angemeldet als Kaffeetasse“ steht. Im Netzwerk-Reiter: Antwort 401 mit WWW-Authenticate: Bearer error="invalid_token", error_description="… Jwt expired …". Im sessionStorage steht ein neueres Access Token als im Header der Anfrage.

  • Beobachtung D: Das Paar hat in Keycloak ein Testkonto probe mit Attribut pseudonym = „Probe“ angelegt. Die Anmeldung gelingt, aber POST /v1/angebote/1/buchungen antwortet mit 403 und „Kein Konto im Mitfahrlabor für Probe“.

a) Analysiere die vier Beobachtungen: Nenne jeweils die Ursache und eine passende Behebung. (12 Punkte)

Je Beobachtung 2 Punkte für die Ursache, 1 Punkt für eine passende Behebung:

  • A: Die Rückkehr-Adresse ist fest auf Port 5173 eingetragen, im Client sind nur 5173 und 4173 als Redirect URI erlaubt; Keycloak lehnt jede andere Adresse ab, bevor es weiterleitet. Behebung: den alten Server beenden und Vite mit --strictPort fest auf 5173 starten (dann bricht Vite ab statt auszuweichen); eine zusätzliche Redirect URI nur, wenn der Port gewollt ist. Die Adresse in Keycloak mit Platzhaltern weit zu öffnen ist keine gute Behebung.

  • B: sessionStorage gehört zu einem Ursprung (Schema, Host, Port). Die Bibliothek speichert state und code_verifier unter 127.0.0.1:5173, Keycloak leitet aber zur fest eingetragenen Adresse localhost:5173 zurück; dort findet sie nichts. Behebung: redirect_uri aus window.location.origin bilden wie im Artikel und die App immer unter derselben Adresse aufrufen; 127.0.0.1 gehört dann nicht in die Redirect URIs.

  • C: auth: sitzung.accessToken übergibt den Wert beim Start. Die Bibliothek erneuert das Token zwar (neuer Wert im Speicher und im Store), der Client schickt aber weiter das erste, nach 5 Minuten abgelaufene. Außerdem fehlt eine Reaktion auf 401, sonst würde die App die abgelaufene Anmeldung melden. Behebung: eine Funktion übergeben, auth: () => sitzung.accessToken, und 401 per Interceptor an den Store melden.

  • D: Keycloak weist nur nach, wer angemeldet ist. Das Backend sucht die Person über das Pseudonym in der eigenen Tabelle nutzer; „Probe“ gibt es dort nicht, deshalb 403 (angemeldet, aber nicht berechtigt), nicht 401. Behebung: das Testkonto in Keycloak mit einem Pseudonym aus data.sql anlegen oder den Datensatz im Backend ergänzen (Aufgabe der Backend-Seite, nicht der Web-App).

Ein Test, der Befund C zeigt, setzt client.setConfig({ auth: sitzung.accessToken }), ändert danach das Token im Store und stellt fest, dass der Header noch Bearer test-token-3 enthält.

b) Für mehr Bequemlichkeit schlägt das Paar zwei Änderungen vor: Die Tokens sollen im localStorage liegen, damit man nach einem Neustart des Browsers angemeldet bleibt. Und das Access Token soll in Keycloak 8 Stunden gelten, „dann gibt es keine Probleme mit dem Ablauf mehr“. Nimm zu beiden Vorschlägen Stellung und nenne für den Wunsch nach weniger Anmeldungen eine bessere Lösung. (6 Punkte)

  • localStorage (2 Punkte): Die Tokens lägen dauerhaft im Browser, auch nach dem Schließen, auf einem gemeinsam genutzten Rechner im Klassenraum also für die nächste Person greifbar; per Cross-Site-Scripting wären sie wie im sessionStorage lesbar, aber länger nutzbar. Abzulehnen.

  • 8 Stunden Laufzeit (2 Punkte): Ein gestohlenes Access Token wäre einen ganzen Schultag lang gültig, und das Backend kann es nicht zurückziehen, weil es nur Signatur und Ablauf prüft. Der eigentliche Fehler (Befund C) bliebe außerdem bestehen und würde nur seltener sichtbar. Abzulehnen.

  • Bessere Lösung (2 Punkte): kurze Access Tokens mit automatischer Erneuerung per Refresh Token (wie im Artikel) und Befund C beheben; die Dauer der Sitzung regelt Keycloak über die Sitzungs-Zeiten des Realms (z. B. „angemeldet bleiben“ bewusst einschalten). Wer Tokens ganz aus dem Browser heraushalten will, nutzt ein Backend for Frontend mit Sitzungs-Cookie.

Bewertet wird die Begründung, nicht das Ergebnis: Eine abweichende, schlüssig begründete Stellungnahme erhält die Punkte.


Hinweis zur Nachnutzung als Open Educational Resource (OER)

Dieser Artikel und seine Texte, Bilder, Grafiken, Code und sonstiger Inhalt sind - sofern nicht anders angegeben - lizenziert unter CC BY 4.0. Nennung gemäß TULLU-Regel bitte wie folgt: “Übungsaufgaben zur Anmeldung mit Keycloak in Vue: Mitfahrgesuche mit Token” von oer-informatik.de (H. Stein), Lizenz: CC BY 4.0. Der Artikel wurde unter https://oer-informatik.de/vue-keycloak-oidc-uebungsaufgaben veröffentlicht, die Quelltexte sind in weiterverarbeitbarer Form verfügbar im Repository unter https://gitlab.com/oer-informatik/programmierung/vue. Erstellt mit KI-Assistenz; von mir umfassend redaktionell überarbeitet, geprüft und freigegeben. Stand: 11.11.2026.

[Kommentare zum Artikel lesen, schreiben] / [Artikel teilen] / [gitlab-Issue zum Artikel schreiben]

Kommentare gerne per Mastodon, Verbesserungsvorschläge per gitlab issue (siehe oben). Beitrag teilen: