backend-go tidak menerbitkan JWT. Token diterbitkan dazoapp (Laravel) dan divalidasi di sini via middlewares/authMiddleware.go:VerifyToken.
Middleware — VerifyToken
Diterapkan global pada group /v1 (routes/routes.go:31):
v1 := app.Group("v1")
v1.Use(middlewares.VerifyToken)Alur validasi (authMiddleware.go:12-109):
1. Excluded paths check
└─ jika path match → bypass auth (c.Next())
2. IP allowlist check
└─ jika IP di ALLOWED_IPS → bypass auth
3. Token presence check
└─ jika Authorization kosong → 401 "Unauthenticated"
4. Parse + verify JWT (HS256, JWT_SECRET)
└─ jika invalid/expired → 401 "Unauthenticated"
5. Cache claims di c.Locals("user", token.Claims)
6. c.Next()Excluded paths — skip auth
33 path di-hardcode di authMiddleware.go:15-50. Jika c.Path() berawalan /v1 + excluded path, request bypass auth sepenuhnya.
| Kategori | Path | Pemanggil |
|---|---|---|
| WhatsApp gateway webhook | callback_message, callback_message_admin, callback_message_crm | WA gateway :5002/:5003 |
Semua route _admin | 16 route (chat_inbox_admin, send_message_admin, dll) | Admin panel (internal) |
Semua route _crm | 16 route (chat_inbox_crm, send_message_crm, dll) | CRM workspace |
IP allowlist bypass
authMiddleware.go:62-80 — IP client dicek terhadap config.Env.AllowedIPs (comma-split dari .env ALLOWED_IPS). Jika match, request bypass auth.
Urutan pembacaan IP:
clientIP := c.Get("X-Real-IP") // 1. header X-Real-IP
if clientIP == "" {
clientIP = c.Get("X-Forwarded-For") // 2. header X-Forwarded-For
}
if clientIP == "" {
clientIP = c.Context().RemoteAddr() // 3. socket remote address
}Verifikasi JWT
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
return []byte(config.Env.JwtSecret), nil
})jwt.Parse memverifikasi signature + expiry. Jika invalid, return 401:
{
"status": "error",
"message": "Unauthenticated"
}Claims
Setelah valid, claims di-cache:
c.Locals("user", token.Claims) // jwt.Claims, bukan *jwt.Tokenutils/utils.go:61-75 mendefinisikan helper GetJwtClaims:
type JwtClaims struct {
Sub int
UserLevel models.UserLevel
}
func GetJwtClaims(c *fiber.Ctx) JwtClaims {
token := c.Locals("user").(*jwt.Token) // BUG: type mismatch
claims := token.Claims.(jwt.MapClaims)
sub := int(claims["sub"].(float64))
userLevel := claims["userLevel"].(string)
// ...
}Handler yang memakai claims
Saat ini tidak ada handler aktif yang membaca claims JWT. Auth hanya sebagai gate (valid/invalid). Authorization berbasis userLevel claim belum diimplementasi di handler Mongo.
Response error
| Status | Kondisi | Body |
|---|---|---|
| 401 | Token kosong | {"status":"error","message":"Unauthenticated"} |
| 401 | Token invalid/expired | {"status":"error","message":"Unauthenticated"} |
| 400 | Error handler Fiber (non-auth) | {"code":400,"message":"<err>"} |
Verifikasi token lokal
# Generate token dari dazoapp
php artisan tinker
>>> JWTAuth::fromUser(User::first());
# Test ke Go API
curl -X POST http://localhost:8081/v1/chat_list \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{"instance_id":"...","phone":"..."}'Yang TIDAK boleh diubah tanpa koordinasi
| Area | Lokasi |
|---|---|
JWT_SECRET | .env — wajib sync dengan dazoapp |
| Algoritma HS256 | Kontrak penerbitan dazoapp |
Claim sub, userLevel | Payload token dazoapp |
| Excluded paths | Jika diubah, WA gateway/admin/CRM bisa 401 |
Langkah berikutnya
- Cara tenant dipisah? Baca Multi-tenant.
- Pola kode? Baca Conventions.