Stack:
- Frontend: React + Vite + Tailwind + MUI
- Backend: NestJS + Prisma
- DB app: PostgreSQL
- Auth: Keycloak (OIDC Authorization Code + PKCE)
Local URLs:
- Frontend:
http://localhost:5173 - Backend API:
http://localhost:3000 - Keycloak:
http://localhost:8080
From backend/:
cd backend
docker compose up -dServices started:
postgres(single DB server for app + Keycloak) onlocalhost:5432keycloakonlocalhost:8080mailhog(legacy/dev utility)
Open http://localhost:8080.
Admin credentials (from compose):
- username:
admin - password:
admin
- Realm name:
doodle
- Realm Settings -> Login
- Set
User registrationtoOFF
- Clients -> Create client
- Client ID:
doodle-frontend - Client type:
OpenID Connect - Access type:
Public - Standard flow:
Enabled - Direct access grants:
Disabled - Root URL:
http://localhost:5173 - Valid redirect URIs:
http://localhost:5173/* - Web origins:
http://localhost:5173
Recommended: Confidential (resource server use case).
- Clients -> Create client
- Client ID:
doodle-api - Client type:
OpenID Connect - Access type:
Confidential - Standard flow: disabled (not required for API)
To include API audience in access tokens issued to frontend:
- Open
doodle-frontendclient - Client scopes / Mappers -> Add mapper
- Mapper type:
Audience - Included Client Audience:
doodle-api - Add to access token:
ON
This ensures aud contains doodle-api.
- Users -> Create new user
- Set username/email
- Enable user
- Credentials -> Set password
- Mark password as
Temporary - Required Actions should include
UPDATE_PASSWORD(add it if missing)
At first login, Keycloak will force password change before returning to the app.
backend/.env should contain:
PORT=3000
DATABASE_URL=postgresql://myuser:mypassword@localhost:5432/mydatabase
KEYCLOAK_ISSUER=http://localhost:8080/realms/doodle
KEYCLOAK_JWKS_URI=http://localhost:8080/realms/doodle/protocol/openid-connect/certs
KEYCLOAK_API_AUDIENCE=doodle-api
KEYCLOAK_ALLOW_AZP_FALLBACK=true
FRONTEND_ORIGIN=http://localhost:5173Run Prisma + API:
cd backend
npm install
npx prisma generate
npx prisma migrate dev
npm run start:devfrontend/.env should contain:
VITE_API_BASE_URL=http://localhost:3000
VITE_KEYCLOAK_URL=http://localhost:8080
VITE_KEYCLOAK_REALM=doodle
VITE_KEYCLOAK_CLIENT_ID=doodle-frontendRun frontend:
cd frontend
npm install
npm run dev- Custom auth endpoints removed (
/auth/login,/auth/signup-request,/auth/verify,/auth/set-password,/auth/refresh,/auth/logout). - Backend now validates Keycloak JWTs using JWKS (
/protocol/openid-connect/certs), issuer, and audience. - Guard sets
req.useras:subemailpreferred_usernamedbUserId
- On authenticated calls, user is upserted in app DB by
keycloakSub+email. - Prisma
Usermodel now uses Keycloak identity fields only.
- Admin creates user in Keycloak with temporary password.
- User clicks Login in frontend.
- Keycloak redirects to password update screen.
- After update, redirect back to app.
- API calls succeed with Bearer token.
Symptom: 401 Invalid token audience.
Fix:
- Verify audience mapper on
doodle-frontendincludesdoodle-apiin access token. - Confirm backend
.envhasKEYCLOAK_API_AUDIENCE=doodle-api.
Symptom: 401 Invalid token issuer.
Fix:
- Ensure backend uses
KEYCLOAK_ISSUER=http://localhost:8080/realms/doodle. - Confirm token
issclaim matches exactly.
Fix:
- Backend
FRONTEND_ORIGINmust behttp://localhost:5173. - Keycloak client web origin must include
http://localhost:5173.
Fix:
- In Keycloak
doodle-frontendclient, set valid redirect URIs tohttp://localhost:5173/*.
Symptom: 401 Token missing email claim.
Fix:
- Ensure frontend login uses
openid profile emailscope. - Ensure Keycloak client scopes include
email.