Skip to content

Runbook: Authentication end-to-end verification (Task 5.3)

Field Value
Authority ADR-011; keycloak-and-auth.md
Task 5.3 Authentication
Scope Verify Keycloak login -> gateway JWKS validation -> identity propagation -> 401 on no token

Per ADR-011 Keycloak is the identity provider and token issuer; identity-service does not mint JWTs. This runbook proves the live flow. The deterministic CI proof lives in the api-gateway tests (JwtClaimsFilterTest, GatewayAuthenticationIntegrationTest).

Prerequisites

cd infra
make auth        # starts core services + Keycloak (compose profile: auth)

The telco-crm realm (roles, the telco-roles claim mapper, the telco-web / telco-gateway clients, and the local demo users) is imported from infra/docker/keycloak/realm/realm-export.json on startup. Realm import skips an already-existing realm, so after editing the realm file run make destroy (drops the volume) before make auth to re-import.

Start the gateway (and config-server + discovery-server it depends on), e.g.:

mvn -f microservices/api-gateway/pom.xml spring-boot:run

Demo users (local only): admin@telco.local / admin (ADMIN), agent@telco.local / agent (CALL_CENTER_AGENT), subscriber@telco.local / subscriber (SUBSCRIBER).

1. Obtain a token (AC 5.3.1)

TOKENS=$(curl -s http://localhost:8085/realms/telco-crm/protocol/openid-connect/token \
  -d grant_type=password \
  -d client_id=telco-gateway \
  -d client_secret=local-dev-secret \
  -d username=admin@telco.local \
  -d password=admin)

echo "$TOKENS" | jq '{has_access: (.access_token != null), has_refresh: (.refresh_token != null)}'
ACCESS=$(echo "$TOKENS" | jq -r .access_token)

Expected: both has_access and has_refresh are true. (telco-gateway has directAccessGrantsEnabled for this password-grant path.)

Decode the access token payload and confirm the required claims:

echo "$ACCESS" | cut -d. -f2 | base64 -d 2>/dev/null | jq '{sub, roles, iss, exp}'

Expected: sub (a UUID), a flat roles array containing ADMIN, iss ending in /realms/telco-crm, and a numeric exp.

2. Reach a protected route with a valid token (AC 5.3.2)

curl -s -o /dev/null -w "%{http_code}\n" \
  -H "Authorization: Bearer $ACCESS" \
  http://localhost:8080/api/v1/users/me

Expected: the gateway validates the token against the realm JWKS and forwards the request with X-User-Id (from sub) and X-User-Roles (from roles) injected. The downstream identity-service endpoint arrives in Sprint 5.5; until then a 404/503 from the gateway still confirms the token was accepted (auth passed before routing). The 200-with-headers path is asserted today by GatewayAuthenticationIntegrationTest.

3. Reject a request without a token (AC 5.3.2)

curl -s -w "\n%{http_code}\n" http://localhost:8080/api/v1/users/me

Expected: HTTP 401 with the ApiResult-shaped envelope {"success":false,"error":{"code":"UNAUTHORIZED","message":"Authentication required"}}.

A malformed/expired token (-H "Authorization: Bearer not-a-jwt") likewise returns 401.

Acceptance criteria mapping

  • 5.3.1: Step 1 - user obtains access + refresh tokens; access token carries sub, flat roles, issuer, expiry.
  • 5.3.2: Step 2 - valid token accepted and identity propagated; Step 3 - missing/invalid token -> 401.