A demonstration of SPNEGO/Negotiate authentication with React 18, featuring a local Kerberos KDC for testing.
┌─────────────┐ ┌─────────────────┐ ┌─────────────┐
│ Browser │────▶│ Express API │────▶│ Kerberos │
│ (React 18) │ │ (SPNEGO auth) │ │ KDC │
└─────────────┘ └─────────────────┘ └─────────────┘
▲ │
└────────────────────────────────────────────┘
Kerberos ticket validation
- Docker and Docker Compose
- A browser that supports SPNEGO (Chrome, Firefox, Safari)
Add these entries to /etc/hosts:
sudo ./scripts/setup-hosts.shOr manually add:
127.0.0.1 kdc.example.com
127.0.0.1 api.example.com
127.0.0.1 app.example.com
docker-compose up -dThis starts:
- KDC (kdc.example.com:88) - MIT Kerberos Key Distribution Center
- API (api.example.com:4000) - Express server with SPNEGO auth
- Client (app.example.com:3000) - React 18 application
# macOS
open -a "Google Chrome" --args \
--auth-server-whitelist="*.example.com" \
--auth-negotiate-delegate-whitelist="*.example.com"
# Linux
google-chrome \
--auth-server-whitelist="*.example.com" \
--auth-negotiate-delegate-whitelist="*.example.com"Navigate to about:config and set:
network.negotiate-auth.trusted-uris=.example.comnetwork.negotiate-auth.delegation-uris=.example.com
# Get a ticket for the test user
docker exec -it kerberos-kdc kinit testuser
# Password: testuser123
# Verify the ticket
docker exec -it kerberos-kdc klistOpen http://app.example.com:3000 in your configured browser.
| Principal | Password |
|---|---|
| [email protected] | testuser123 |
| admin/[email protected] | adminpassword123 |
GET /health- Health checkGET /auth/status- Check authentication status (optional auth)POST /auth/logout- Logout (clears client state)
GET /auth/login- Force authenticationGET /api/whoami- Get authenticated principalGET /api/data- Get protected dataPOST /api/data- Create data
# Test SPNEGO authentication
curl -v --negotiate -u : http://api.example.com:4000/api/whoami
# Check auth status
curl http://api.example.com:4000/auth/statuskerberos-sandbox/
├── docker/
│ └── kdc/
│ ├── Dockerfile # MIT Kerberos KDC image
│ ├── krb5.conf # Kerberos configuration
│ ├── kadm5.acl # Admin ACL
│ └── init-kdc.sh # KDC initialization script
├── docker-compose.yml # Service orchestration
├── server/
│ ├── package.json
│ ├── tsconfig.json
│ ├── Dockerfile
│ └── src/
│ ├── index.ts # Express server entry
│ ├── middleware/
│ │ └── spnego.ts # SPNEGO authentication
│ └── routes/
│ ├── auth.ts # Auth endpoints
│ └── api.ts # Protected API
├── client/
│ ├── package.json
│ ├── vite.config.ts
│ ├── Dockerfile
│ └── src/
│ ├── main.tsx # App entry
│ ├── App.tsx # Main component
│ ├── api/
│ │ └── client.ts # API client with credentials
│ ├── hooks/
│ │ └── useAuth.ts # Auth state hook
│ └── components/
│ ├── AuthStatus.tsx
│ └── ProtectedContent.tsx
├── scripts/
│ └── setup-hosts.sh # Host file setup
└── README.md
Ensure the service principal matches. Check with:
docker exec -it kerberos-kdc kadmin.local -q "listprincs"- Verify you have a valid ticket:
docker exec -it kerberos-kdc klist - Ensure the keytab is accessible:
docker exec -it kerberos-kdc klist -kt /etc/krb5.keytab - Check browser is configured for SPNEGO
The API server allows credentials from http://app.example.com:3000. Ensure you're accessing the app from this exact URL.
- Verify browser configuration (see step 3)
- Try a fresh browser profile
- Clear browser cache and credentials
# Start KDC only
docker-compose up -d kdc
# Run API locally (requires krb5-dev)
cd server && npm install && npm run dev
# Run client locally
cd client && npm install && npm run devdocker-compose logs -f kdc
docker-compose logs -f api
docker-compose logs -f clientdocker-compose build --no-cache
docker-compose up -d- The
kerberosnpm package requires native Kerberos libraries (installed in the Docker image) - SPNEGO authentication is stateless - the browser sends credentials on every request
- For production, use HTTPS and proper certificate handling
- The test KDC uses a simple master password - not suitable for production