D
Engineering

Auth & JWT

JWT HS256 — validasi-only. Token diterbitkan dazoapp (Laravel), divalidasi di sini. Excluded paths skip auth, IP allowlist bypass, algoritma tidak di-pin.

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):

go
v1 := app.Group("v1")
v1.Use(middlewares.VerifyToken)

Alur validasi (authMiddleware.go:12-109):

text
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.

KategoriPathPemanggil
WhatsApp gateway webhookcallback_message, callback_message_admin, callback_message_crmWA gateway :5002/:5003
Semua route _admin16 route (chat_inbox_admin, send_message_admin, dll)Admin panel (internal)
Semua route _crm16 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:

go
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

go
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:

json
{
  "status": "error",
  "message": "Unauthenticated"
}

Claims

Setelah valid, claims di-cache:

go
c.Locals("user", token.Claims)   // jwt.Claims, bukan *jwt.Token

utils/utils.go:61-75 mendefinisikan helper GetJwtClaims:

go
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

StatusKondisiBody
401Token kosong{"status":"error","message":"Unauthenticated"}
401Token invalid/expired{"status":"error","message":"Unauthenticated"}
400Error handler Fiber (non-auth){"code":400,"message":"<err>"}

Verifikasi token lokal

bash
# 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

AreaLokasi
JWT_SECRET.env — wajib sync dengan dazoapp
Algoritma HS256Kontrak penerbitan dazoapp
Claim sub, userLevelPayload token dazoapp
Excluded pathsJika diubah, WA gateway/admin/CRM bisa 401

Langkah berikutnya