ปกป้อง Fiber API ของคุณด้วยการควบคุมการเข้าถึงตามบทบาท (RBAC) และการตรวจสอบ JWT
คู่มือนี้จะช่วยให้คุณนำการอนุญาต (Authorization) ไปใช้เพื่อรักษาความปลอดภัยให้กับ Fiber API ของคุณ โดยใช้ การควบคุมการเข้าถึงตามบทบาท (RBAC) และ JSON Web Tokens (JWTs) ที่ออกโดย Logto
ก่อนเริ่มต้น
แอปพลิเคชันไคลเอนต์ของคุณจำเป็นต้องขอรับโทเค็นการเข้าถึง (Access tokens) จาก Logto หากคุณยังไม่ได้ตั้งค่าการเชื่อมต่อกับไคลเอนต์ โปรดดู เริ่มต้นอย่างรวดเร็ว สำหรับ React, Vue, Angular หรือเฟรมเวิร์กฝั่งไคลเอนต์อื่น ๆ หรือดู คู่มือเครื่องต่อเครื่อง สำหรับการเข้าถึงแบบเซิร์ฟเวอร์ต่อเซิร์ฟเวอร์
คู่มือนี้เน้นที่ การตรวจสอบโทเค็นฝั่งเซิร์ฟเวอร์ ในแอป Fiber ของคุณ
สิ่งที่คุณจะได้เรียนรู้
- การตรวจสอบ JWT: เรียนรู้วิธีตรวจสอบโทเค็นการเข้าถึง (Access tokens) และดึงข้อมูลการยืนยันตัวตน (Authentication)
 - การสร้าง Middleware: สร้าง middleware ที่นำกลับมาใช้ซ้ำได้สำหรับการปกป้อง API
 - โมเดลสิทธิ์ (Permission models): เข้าใจและนำรูปแบบการอนุญาต (Authorization) ที่แตกต่างกันไปใช้:
- ทรัพยากร API ระดับโกลบอลสำหรับ endpoint ทั่วทั้งแอปพลิเคชัน
 - สิทธิ์ขององค์กรสำหรับควบคุมฟีเจอร์เฉพาะผู้เช่า (tenant)
 - ทรัพยากร API ระดับองค์กรสำหรับการเข้าถึงข้อมูลแบบหลายผู้เช่า (multi-tenant)
 
 - การผสาน RBAC: บังคับใช้สิทธิ์และขอบเขต (Scopes) ตามบทบาท (RBAC) ใน endpoint ของ API ของคุณ
 
ข้อกำหนดเบื้องต้น
- ติดตั้ง Go เวอร์ชันเสถียรล่าสุด
 - มีความเข้าใจพื้นฐานเกี่ยวกับ Fiber และการพัฒนาเว็บ API
 - ตั้งค่าแอป Logto เรียบร้อยแล้ว (ดู เริ่มต้นอย่างรวดเร็ว หากยังไม่ได้ตั้งค่า)
 
ภาพรวมของโมเดลสิทธิ์ (Permission models overview)
ก่อนดำเนินการปกป้องทรัพยากร ให้เลือกโมเดลสิทธิ์ที่เหมาะสมกับสถาปัตยกรรมแอปพลิเคชันของคุณ ซึ่งสอดคล้องกับ สถานการณ์การอนุญาต (authorization scenarios) หลักสามแบบของ Logto:
- ทรัพยากร API ระดับโกลบอล (Global API resources)
 - สิทธิ์ขององค์กร (ไม่ใช่ API) (Organization (non-API) permissions)
 - ทรัพยากร API ระดับองค์กร (Organization-level API resources)
 

- กรณีการใช้งาน: ปกป้องทรัพยากร API ที่ใช้ร่วมกันทั่วทั้งแอปพลิเคชัน (ไม่เฉพาะองค์กร)
 - ประเภทโทเค็น: โทเค็นการเข้าถึง (Access token) ที่มีผู้รับ (audience) ระดับโกลบอล
 - ตัวอย่าง: Public APIs, บริการหลักของผลิตภัณฑ์, จุดเชื่อมต่อสำหรับผู้ดูแลระบบ
 - เหมาะสำหรับ: ผลิตภัณฑ์ SaaS ที่มี API ใช้ร่วมกันโดยลูกค้าทุกคน, microservices ที่ไม่มีการแยก tenant
 - เรียนรู้เพิ่มเติม: ปกป้องทรัพยากร API ระดับโกลบอล
 

- กรณีการใช้งาน: ควบคุมการกระทำเฉพาะองค์กร, ฟีเจอร์ UI, หรือ business logic (ไม่ใช่ API)
 - ประเภทโทเค็น: โทเค็นองค์กร (Organization token) ที่มีผู้รับ (audience) เฉพาะองค์กร
 - ตัวอย่าง: การจำกัดฟีเจอร์, สิทธิ์แดชบอร์ด, การควบคุมการเชิญสมาชิก
 - เหมาะสำหรับ: SaaS หลายผู้เช่า (multi-tenant) ที่มีฟีเจอร์และเวิร์กโฟลว์เฉพาะองค์กร
 - เรียนรู้เพิ่มเติม: ปกป้องสิทธิ์ขององค์กร (ไม่ใช่ API)
 

- กรณีการใช้งาน: ปกป้องทรัพยากร API ที่เข้าถึงได้ในบริบทขององค์กรเฉพาะ
 - ประเภทโทเค็น: โทเค็นองค์กร (Organization token) ที่มีผู้รับเป็นทรัพยากร API + บริบทองค์กร
 - ตัวอย่าง: API หลายผู้เช่า, จุดเชื่อมต่อข้อมูลที่จำกัดขอบเขตองค์กร, microservices เฉพาะ tenant
 - เหมาะสำหรับ: SaaS หลายผู้เช่าที่ข้อมูล API ถูกจำกัดขอบเขตองค์กร
 - เรียนรู้เพิ่มเติม: ปกป้องทรัพยากร API ระดับองค์กร
 
💡 เลือกโมเดลของคุณก่อนดำเนินการต่อ - การนำไปใช้จะอ้างอิงแนวทางที่คุณเลือกตลอดคู่มือนี้
ขั้นตอนเตรียมความพร้อมอย่างรวดเร็ว
กำหนดค่าทรัพยากรและสิทธิ์ของ Logto
- ทรัพยากร API ระดับโกลบอล
 - สิทธิ์ขององค์กร (ไม่ใช่ API)
 - ทรัพยากร API ระดับองค์กร
 
- สร้างทรัพยากร API: ไปที่ Console → ทรัพยากร API และลงทะเบียน API ของคุณ (เช่น 
https://api.yourapp.com) - กำหนดสิทธิ์: เพิ่มขอบเขต (scopes) เช่น 
read:products,write:orders– ดู กำหนดทรัพยากร API พร้อมสิทธิ์ - สร้างบทบาทระดับโกลบอล: ไปที่ Console → บทบาท และสร้างบทบาทที่รวมสิทธิ์ API ของคุณ – ดู กำหนดค่าบทบาทระดับโกลบอล
 - กำหนดบทบาท: กำหนดบทบาทให้กับผู้ใช้หรือแอป M2M ที่ต้องการเข้าถึง API
 
- กำหนดสิทธิ์ขององค์กร: สร้างสิทธิ์ขององค์กรที่ไม่ใช่ API เช่น 
invite:member,manage:billingในเทมเพลตขององค์กร - ตั้งค่าบทบาทขององค์กร: กำหนดค่าเทมเพลตขององค์กรด้วยบทบาทเฉพาะองค์กรและกำหนดสิทธิ์ให้กับบทบาทเหล่านั้น
 - กำหนดบทบาทขององค์กร: กำหนดผู้ใช้ให้กับบทบาทขององค์กรในแต่ละบริบทขององค์กร
 
- สร้างทรัพยากร API: ลงทะเบียนทรัพยากร API ของคุณเช่นเดียวกับข้างต้น แต่จะใช้ในบริบทขององค์กร
 - กำหนดสิทธิ์: เพิ่มขอบเขต (scopes) เช่น 
read:data,write:settingsที่จำกัดในบริบทขององค์กร - กำหนดค่าเทมเพลตขององค์กร: ตั้งค่าบทบาทขององค์กรที่รวมสิทธิ์ของทรัพยากร API ของคุณ
 - กำหนดบทบาทขององค์กร: กำหนดผู้ใช้หรือแอป M2M ให้กับบทบาทขององค์กรที่รวมสิทธิ์ API
 - ตั้งค่าหลายผู้เช่า: ตรวจสอบให้แน่ใจว่า API ของคุณสามารถจัดการข้อมูลและการตรวจสอบที่จำกัดในแต่ละองค์กรได้
 
เริ่มต้นด้วย คู่มือการควบคุมการเข้าถึงตามบทบาท (RBAC) ของเรา สำหรับคำแนะนำการตั้งค่าแบบทีละขั้นตอน
อัปเดตแอปพลิเคชันฝั่งไคลเอนต์ของคุณ
ร้องขอขอบเขต (scopes) ที่เหมาะสมในไคลเอนต์ของคุณ:
- การยืนยันตัวตนผู้ใช้: อัปเดตแอปของคุณ → เพื่อร้องขอขอบเขต API และ/หรือบริบทขององค์กร
 - เครื่องต่อเครื่อง: กำหนดค่า M2M scopes → สำหรับการเข้าถึงระหว่างเซิร์ฟเวอร์
 
กระบวนการนี้มักเกี่ยวข้องกับการอัปเดตการกำหนดค่าไคลเอนต์ของคุณเพื่อรวมหนึ่งหรือมากกว่ารายการต่อไปนี้:
- พารามิเตอร์ 
scopeในกระบวนการ OAuth - พารามิเตอร์ 
resourceสำหรับการเข้าถึงทรัพยากร API organization_idสำหรับบริบทขององค์กร
ตรวจสอบให้แน่ใจว่าผู้ใช้หรือแอป M2M ที่คุณทดสอบได้รับการกำหนดบทบาทหรือบทบาทขององค์กรที่มีสิทธิ์ที่จำเป็นสำหรับ API ของคุณแล้ว
เริ่มต้นโปรเจกต์ API ของคุณ
ในการเริ่มต้นโปรเจกต์ Go ใหม่ด้วย Fiber คุณสามารถทำตามขั้นตอนดังนี้:
go mod init your-api-name
go get github.com/gofiber/fiber/v2
จากนั้น สร้างเซิร์ฟเวอร์ Fiber พื้นฐาน:
package main
import (
    "log"
    "github.com/gofiber/fiber/v2"
)
func main() {
    app := fiber.New()
    log.Fatal(app.Listen(":3000"))
}
ดูเอกสารของ Fiber สำหรับรายละเอียดเพิ่มเติมเกี่ยวกับการตั้งค่าเส้นทาง (routes), มิดเดิลแวร์ (middleware) และฟีเจอร์อื่น ๆ
กำหนดค่าคงที่และยูทิลิตี้
กำหนดค่าคงที่และยูทิลิตี้ที่จำเป็นในโค้ดของคุณเพื่อจัดการการดึงและตรวจสอบโทเค็น คำขอที่ถูกต้องต้องมี header Authorization ในรูปแบบ Bearer <access_token>
package main
import (
    "fmt"
    "net/http"
    "strings"
)
const (
    JWKS_URI = "https://your-tenant.logto.app/oidc/jwks"
    ISSUER   = "https://your-tenant.logto.app/oidc"
)
type AuthorizationError struct {
    Message string
    Status  int
}
func (e *AuthorizationError) Error() string {
    return e.Message
}
func NewAuthorizationError(message string, status ...int) *AuthorizationError {
    statusCode := http.StatusForbidden // ค่าเริ่มต้นเป็น 403 Forbidden
    if len(status) > 0 {
        statusCode = status[0]
    }
    return &AuthorizationError{
        Message: message,
        Status:  statusCode,
    }
}
func extractBearerTokenFromHeaders(r *http.Request) (string, error) {
    const bearerPrefix = "Bearer "
    authorization := r.Header.Get("Authorization")
    if authorization == "" {
        return "", NewAuthorizationError("ไม่มี Authorization header", http.StatusUnauthorized)
    }
    if !strings.HasPrefix(authorization, bearerPrefix) {
        return "", NewAuthorizationError(fmt.Sprintf("Authorization header ต้องขึ้นต้นด้วย \"%s\"", bearerPrefix), http.StatusUnauthorized)
    }
    return strings.TrimPrefix(authorization, bearerPrefix), nil
}
ดึงข้อมูลเกี่ยวกับ Logto tenant ของคุณ
คุณจะต้องใช้ค่าต่อไปนี้เพื่อยืนยันโทเค็นที่ออกโดย Logto:
- URI ของ JSON Web Key Set (JWKS): URL ไปยัง public keys ของ Logto ใช้สำหรับตรวจสอบลายเซ็นของ JWT
 - ผู้ออก (Issuer): ค่าผู้ออกที่คาดหวัง (OIDC URL ของ Logto)
 
ขั้นแรก ให้ค้นหา endpoint ของ Logto tenant ของคุณ คุณสามารถหาได้จากหลายที่:
- ใน Logto Console ที่ Settings → Domains
 - ในการตั้งค่าแอปพลิเคชันใด ๆ ที่คุณตั้งค่าใน Logto, Settings → Endpoints & Credentials
 
ดึงค่าจาก OpenID Connect discovery endpoint
ค่าทั้งหมดนี้สามารถดึงได้จาก OpenID Connect discovery endpoint ของ Logto:
https://<your-logto-endpoint>/oidc/.well-known/openid-configuration
ตัวอย่างการตอบกลับ (ละเว้นฟิลด์อื่นเพื่อความกระชับ):
{
  "jwks_uri": "https://your-tenant.logto.app/oidc/jwks",
  "issuer": "https://your-tenant.logto.app/oidc"
}
เขียนค่าคงที่ในโค้ดของคุณ (ไม่แนะนำ)
เนื่องจาก Logto ไม่อนุญาตให้ปรับแต่ง JWKS URI หรือผู้ออก (issuer) คุณสามารถเขียนค่าคงที่เหล่านี้ไว้ในโค้ดของคุณได้ อย่างไรก็ตาม ไม่แนะนำให้ใช้วิธีนี้ในแอปพลิเคชัน production เพราะอาจเพิ่มภาระในการดูแลรักษาหากมีการเปลี่ยนแปลงค่าคอนฟิกในอนาคต
- JWKS URI: 
https://<your-logto-endpoint>/oidc/jwks - ผู้ออก (Issuer): 
https://<your-logto-endpoint>/oidc 
ตรวจสอบโทเค็นและสิทธิ์ (permissions)
หลังจากดึงโทเค็นและดึงข้อมูล OIDC config แล้ว ให้ตรวจสอบสิ่งต่อไปนี้:
- ลายเซ็น (Signature): JWT ต้องถูกต้องและลงนามโดย Logto (ผ่าน JWKS)
 - ผู้ออก (Issuer): ต้องตรงกับผู้ออกของ Logto tenant ของคุณ
 - ผู้รับ (Audience): ต้องตรงกับตัวบ่งชี้ทรัพยากร API ที่ลงทะเบียนใน Logto หรือบริบทขององค์กรหากเกี่ยวข้อง
 - วันหมดอายุ (Expiration): โทเค็นต้องไม่หมดอายุ
 - สิทธิ์ (ขอบเขต) (Permissions (scopes)): โทเค็นต้องมีขอบเขตที่จำเป็นสำหรับ API / การกระทำของคุณ ขอบเขตจะเป็นสตริงที่คั่นด้วยช่องว่างใน 
scopeการอ้างสิทธิ์ (claim) - บริบทองค์กร (Organization context): หากปกป้องทรัพยากร API ระดับองค์กร ให้ตรวจสอบการอ้างสิทธิ์ 
organization_id 
ดู JSON Web Token เพื่อเรียนรู้เพิ่มเติมเกี่ยวกับโครงสร้างและการอ้างสิทธิ์ของ JWT
สิ่งที่ต้องตรวจสอบสำหรับแต่ละโมเดลสิทธิ์ (What to check for each permission model)
การอ้างสิทธิ์ (claims) และกฎการตรวจสอบจะแตกต่างกันไปตามโมเดลสิทธิ์:
- ทรัพยากร API ระดับโกลบอล (Global API resources)
 - สิทธิ์ขององค์กร (ไม่ใช่ API) (Organization (non-API) permissions)
 - ทรัพยากร API ระดับองค์กร (Organization-level API resources)
 
- การอ้างสิทธิ์ผู้รับ (
aud): ตัวบ่งชี้ทรัพยากร API - การอ้างสิทธิ์องค์กร (
organization_id): ไม่มี - ขอบเขต (สิทธิ์) ที่ต้องตรวจสอบ (
scope): สิทธิ์ของทรัพยากร API 
- การอ้างสิทธิ์ผู้รับ (
aud):urn:logto:organization:<id>(บริบทองค์กรอยู่ในการอ้างสิทธิ์aud) - การอ้างสิทธิ์องค์กร (
organization_id): ไม่มี - ขอบเขต (สิทธิ์) ที่ต้องตรวจสอบ (
scope): สิทธิ์ขององค์กร 
- การอ้างสิทธิ์ผู้รับ (
aud): ตัวบ่งชี้ทรัพยากร API - การอ้างสิทธิ์องค์กร (
organization_id): รหัสองค์กร (ต้องตรงกับคำขอ) - ขอบเขต (สิทธิ์) ที่ต้องตรวจสอบ (
scope): สิทธิ์ของทรัพยากร API 
สำหรับสิทธิ์ขององค์กรที่ไม่ใช่ API บริบทขององค์กรจะแสดงโดยการอ้างสิทธิ์ aud (เช่น
urn:logto:organization:abc123) การอ้างสิทธิ์ organization_id จะมีเฉพาะในโทเค็นทรัพยากร API
ระดับองค์กรเท่านั้น
ควรตรวจสอบทั้งสิทธิ์ (ขอบเขต) และบริบท (ผู้รับ, องค์กร) เสมอ เพื่อความปลอดภัยของ API แบบหลายผู้เช่า
เพิ่มตรรกะการตรวจสอบ
เราใช้ github.com/lestrrat-go/jwx สำหรับตรวจสอบความถูกต้องของ JWTs หากคุณยังไม่ได้ติดตั้ง ให้ติดตั้งดังนี้:
go mod init your-project
go get github.com/lestrrat-go/jwx/v3
ก่อนอื่น เพิ่มคอมโพเนนต์ที่ใช้ร่วมกันเหล่านี้ลงใน auth_middleware.go ของคุณ:
import (
    "context"
    "strings"
    "time"
    "github.com/lestrrat-go/jwx/v3/jwk"
    "github.com/lestrrat-go/jwx/v3/jwt"
)
var jwkSet jwk.Set
func init() {
    // เริ่มต้นแคช JWKS
    ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
    defer cancel()
    var err error
    jwkSet, err = jwk.Fetch(ctx, JWKS_URI)
    if err != nil {
        panic("ดึง JWKS ไม่สำเร็จ: " + err.Error())
    }
}
// validateJWT ตรวจสอบ JWT และคืนค่า token ที่แปลงแล้ว
func validateJWT(tokenString string) (jwt.Token, error) {
    token, err := jwt.Parse([]byte(tokenString), jwt.WithKeySet(jwkSet))
    if err != nil {
        return nil, NewAuthorizationError("โทเค็นไม่ถูกต้อง: "+err.Error(), http.StatusUnauthorized)
    }
    // ตรวจสอบผู้ออก (issuer)
    if token.Issuer() != ISSUER {
        return nil, NewAuthorizationError("ผู้ออกไม่ถูกต้อง", http.StatusUnauthorized)
    }
    if err := verifyPayload(token); err != nil {
        return nil, err
    }
    return token, nil
}
// ฟังก์ชันช่วยเหลือสำหรับดึงข้อมูลจากโทเค็น
func getStringClaim(token jwt.Token, key string) string {
    if val, ok := token.Get(key); ok {
        if str, ok := val.(string); ok {
            return str
        }
    }
    return ""
}
func getScopesFromToken(token jwt.Token) []string {
    if val, ok := token.Get("scope"); ok {
        if scope, ok := val.(string); ok && scope != "" {
            return strings.Split(scope, " ")
        }
    }
    return []string{}
}
func getAudienceFromToken(token jwt.Token) []string {
    return token.Audience()
}
จากนั้น ให้เขียน middleware เพื่อตรวจสอบ access token:
import (
    "net/http"
    "github.com/gofiber/fiber/v2"
)
func VerifyAccessToken(c *fiber.Ctx) error {
    // แปลงคำขอ fiber เป็น http.Request เพื่อความเข้ากันได้
    req := &http.Request{
        Header: make(http.Header),
    }
    req.Header.Set("Authorization", c.Get("Authorization"))
    tokenString, err := extractBearerTokenFromHeaders(req)
    if err != nil {
        authErr := err.(*AuthorizationError)
        return c.Status(authErr.Status).JSON(fiber.Map{"error": authErr.Message})
    }
    token, err := validateJWT(tokenString)
    if err != nil {
        authErr := err.(*AuthorizationError)
        return c.Status(authErr.Status).JSON(fiber.Map{"error": authErr.Message})
    }
    // เก็บ token ใน locals เพื่อใช้งานทั่วไป
    c.Locals("auth", token)
    return c.Next()
}
ตามโมเดลสิทธิ์ของคุณ คุณอาจต้องใช้ตรรกะ verifyPayload ที่แตกต่างกัน:
- ทรัพยากร API ระดับโกลบอล
 - สิทธิ์ขององค์กร (ไม่ใช่ API)
 - ทรัพยากร API ระดับองค์กร
 
func verifyPayload(token jwt.Token) error {
    // ตรวจสอบว่า audience claim ตรงกับตัวบ่งชี้ทรัพยากร API ของคุณ
    if !hasAudience(token, "https://your-api-resource-indicator") {
        return NewAuthorizationError("audience ไม่ถูกต้อง")
    }
    // ตรวจสอบ scope ที่จำเป็นสำหรับทรัพยากร API ระดับโกลบอล
    requiredScopes := []string{"api:read", "api:write"} // เปลี่ยนเป็น scope ที่คุณต้องการจริง
    if !hasRequiredScopes(token, requiredScopes) {
        return NewAuthorizationError("scope ไม่เพียงพอ")
    }
    return nil
}
func verifyPayload(token jwt.Token) error {
    // ตรวจสอบว่า audience claim อยู่ในรูปแบบขององค์กร
    if !hasOrganizationAudience(token) {
        return NewAuthorizationError("audience สำหรับสิทธิ์องค์กรไม่ถูกต้อง")
    }
    // ตรวจสอบว่า organization ID ตรงกับ context (คุณอาจต้องดึงจาก request context)
    expectedOrgID := "your-organization-id" // ดึงจาก request context
    if !hasMatchingOrganization(token, expectedOrgID) {
        return NewAuthorizationError("Organization ID ไม่ตรงกัน")
    }
    // ตรวจสอบ scope ที่จำเป็นสำหรับองค์กร
    requiredScopes := []string{"invite:users", "manage:settings"} // เปลี่ยนเป็น scope ที่คุณต้องการจริง
    if !hasRequiredScopes(token, requiredScopes) {
        return NewAuthorizationError("scope ขององค์กรไม่เพียงพอ")
    }
    return nil
}
func verifyPayload(token jwt.Token) error {
    // ตรวจสอบว่า audience claim ตรงกับตัวบ่งชี้ทรัพยากร API ของคุณ
    if !hasAudience(token, "https://your-api-resource-indicator") {
        return NewAuthorizationError("audience สำหรับทรัพยากร API ระดับองค์กรไม่ถูกต้อง")
    }
    // ตรวจสอบว่า organization ID ตรงกับ context (คุณอาจต้องดึงจาก request context)
    expectedOrgID := "your-organization-id" // ดึงจาก request context
    if !hasMatchingOrganizationID(token, expectedOrgID) {
        return NewAuthorizationError("Organization ID ไม่ตรงกัน")
    }
    // ตรวจสอบ scope ที่จำเป็นสำหรับทรัพยากร API ระดับองค์กร
    requiredScopes := []string{"api:read", "api:write"} // เปลี่ยนเป็น scope ที่คุณต้องการจริง
    if !hasRequiredScopes(token, requiredScopes) {
        return NewAuthorizationError("scope สำหรับทรัพยากร API ระดับองค์กรไม่เพียงพอ")
    }
    return nil
}
เพิ่มฟังก์ชันช่วยเหลือเหล่านี้สำหรับตรวจสอบ payload:
// hasAudience ตรวจสอบว่าโทเค็นมี audience ที่ระบุหรือไม่
func hasAudience(token jwt.Token, expectedAud string) bool {
    audiences := token.Audience()
    for _, aud := range audiences {
        if aud == expectedAud {
            return true
        }
    }
    return false
}
// hasOrganizationAudience ตรวจสอบว่าโทเค็นมี audience ในรูปแบบองค์กรหรือไม่
func hasOrganizationAudience(token jwt.Token) bool {
    audiences := token.Audience()
    for _, aud := range audiences {
        if strings.HasPrefix(aud, "urn:logto:organization:") {
            return true
        }
    }
    return false
}
// hasRequiredScopes ตรวจสอบว่าโทเค็นมี scope ที่จำเป็นครบหรือไม่
func hasRequiredScopes(token jwt.Token, requiredScopes []string) bool {
    scopes := getScopesFromToken(token)
    for _, required := range requiredScopes {
        found := false
        for _, scope := range scopes {
            if scope == required {
                found = true
                break
            }
        }
        if !found {
            return false
        }
    }
    return true
}
// hasMatchingOrganization ตรวจสอบว่า audience ของโทเค็นตรงกับองค์กรที่ต้องการหรือไม่
func hasMatchingOrganization(token jwt.Token, expectedOrgID string) bool {
    expectedAud := fmt.Sprintf("urn:logto:organization:%s", expectedOrgID)
    return hasAudience(token, expectedAud)
}
// hasMatchingOrganizationID ตรวจสอบว่า organization_id ในโทเค็นตรงกับที่ต้องการหรือไม่
func hasMatchingOrganizationID(token jwt.Token, expectedOrgID string) bool {
    orgID := getStringClaim(token, "organization_id")
    return orgID == expectedOrgID
}
นำ middleware ไปใช้กับ API ของคุณ
ตอนนี้ ให้นำ middleware ไปใช้กับเส้นทาง API ที่ต้องการป้องกันของคุณ
package main
import (
    "github.com/gofiber/fiber/v2"
    "github.com/lestrrat-go/jwx/v3/jwt"
)
func main() {
    app := fiber.New()
    // ใช้งาน middleware กับเส้นทางที่ต้องการป้องกัน
    app.Get("/api/protected", VerifyAccessToken, func(c *fiber.Ctx) error {
        // ข้อมูลโทเค็นการเข้าถึง (Access token) สามารถเข้าถึงได้โดยตรงจาก locals
        tokenInterface := c.Locals("auth")
        if tokenInterface == nil {
            return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "ไม่พบโทเค็น (Token not found)"})
        }
        token := tokenInterface.(jwt.Token)
        return c.JSON(fiber.Map{
            "sub":             token.Subject(),
            "client_id":       getStringClaim(token, "client_id"),
            "organization_id": getStringClaim(token, "organization_id"),
            "scopes":          getScopesFromToken(token),
            "audience":        getAudienceFromToken(token),
        })
    })
    app.Listen(":8080")
}
หรือใช้กลุ่มเส้นทาง (route groups):
package main
import (
    "github.com/gofiber/fiber/v2"
    "github.com/lestrrat-go/jwx/v3/jwt"
)
func main() {
    app := fiber.New()
    // สร้างกลุ่มเส้นทางที่ต้องการป้องกัน
    api := app.Group("/api", VerifyAccessToken)
    api.Get("/protected", func(c *fiber.Ctx) error {
        // ข้อมูลโทเค็นการเข้าถึง (Access token) สามารถเข้าถึงได้โดยตรงจาก locals
        token := c.Locals("auth").(jwt.Token)
        return c.JSON(fiber.Map{
            "sub":             token.Subject(),
            "client_id":       getStringClaim(token, "client_id"),
            "organization_id": getStringClaim(token, "organization_id"),
            "scopes":          getScopesFromToken(token),
            "audience":        getAudienceFromToken(token),
            "message":         "เข้าถึงข้อมูลที่ป้องกันสำเร็จ (Protected data accessed successfully)",
        })
    })
    app.Listen(":8080")
}
ทดสอบ API ที่ได้รับการป้องกันของคุณ
รับโทเค็นการเข้าถึง (Access tokens)
จากแอปพลิเคชันไคลเอนต์ของคุณ: หากคุณได้ตั้งค่าการเชื่อมต่อไคลเอนต์แล้ว แอปของคุณจะสามารถรับโทเค็นได้โดยอัตโนมัติ ดึงโทเค็นการเข้าถึงและนำไปใช้ในคำขอ API
สำหรับการทดสอบด้วย curl / Postman:
- 
โทเค็นผู้ใช้: ใช้เครื่องมือสำหรับนักพัฒนาของแอปไคลเอนต์ของคุณเพื่อคัดลอกโทเค็นการเข้าถึงจาก localStorage หรือแท็บ network
 - 
โทเค็นเครื่องต่อเครื่อง: ใช้ client credentials flow ตัวอย่างที่ไม่เป็นทางการโดยใช้ curl:
curl -X POST https://your-tenant.logto.app/oidc/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=client_credentials" \
-d "client_id=your-m2m-client-id" \
-d "client_secret=your-m2m-client-secret" \
-d "resource=https://your-api-resource-indicator" \
-d "scope=api:read api:write"คุณอาจต้องปรับพารามิเตอร์
resourceและscopeให้ตรงกับทรัพยากร API และสิทธิ์ของคุณ; อาจต้องใช้พารามิเตอร์organization_idหาก API ของคุณอยู่ในขอบเขตองค์กร 
ต้องการตรวจสอบเนื้อหาโทเค็นใช่ไหม? ใช้ JWT decoder ของเราเพื่อถอดรหัสและตรวจสอบ JWT ของคุณ
ทดสอบ endpoint ที่ได้รับการป้องกัน
คำขอที่มีโทเค็นถูกต้อง
curl -H "Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..." \
  http://localhost:3000/api/protected
ผลลัพธ์ที่คาดหวัง:
{
  "auth": {
    "sub": "user123",
    "clientId": "app456",
    "organizationId": "org789",
    "scopes": ["api:read", "api:write"],
    "audience": ["https://your-api-resource-indicator"]
  }
}
ไม่มีโทเค็น
curl http://localhost:3000/api/protected
ผลลัพธ์ที่คาดหวัง (401):
{
  "error": "Authorization header is missing"
}
โทเค็นไม่ถูกต้อง
curl -H "Authorization: Bearer invalid-token" \
  http://localhost:3000/api/protected
ผลลัพธ์ที่คาดหวัง (401):
{
  "error": "Invalid token"
}
การทดสอบเฉพาะโมเดลสิทธิ์ (Permission model-specific testing)
- ทรัพยากร API ระดับโกลบอล (Global API resources)
 - สิทธิ์ขององค์กร (ไม่ใช่ API) (Organization (non-API) permissions)
 - ทรัพยากร API ระดับองค์กร (Organization-level API resources)
 
กรณีทดสอบสำหรับ API ที่ได้รับการป้องกันด้วย global scopes:
- ขอบเขตถูกต้อง: ทดสอบด้วยโทเค็นที่มีขอบเขต API ที่ต้องการ (เช่น 
api:read,api:write) - ขาดขอบเขต: คาดหวัง 403 Forbidden เมื่อโทเค็นไม่มีขอบเขตที่จำเป็น
 - audience ไม่ถูกต้อง: คาดหวัง 403 Forbidden เมื่อ audience ไม่ตรงกับทรัพยากร API
 
# โทเค็นที่ขาดขอบเขต - คาดหวัง 403
curl -H "Authorization: Bearer token-without-required-scopes" \
  http://localhost:3000/api/protected
กรณีทดสอบสำหรับการควบคุมการเข้าถึงเฉพาะองค์กร:
- โทเค็นองค์กรถูกต้อง: ทดสอบด้วยโทเค็นที่มี context ขององค์กรที่ถูกต้อง (organization ID และ scopes)
 - ขาดขอบเขต: คาดหวัง 403 Forbidden เมื่อผู้ใช้ไม่มีสิทธิ์สำหรับการกระทำที่ร้องขอ
 - องค์กรไม่ถูกต้อง: คาดหวัง 403 Forbidden เมื่อ audience ไม่ตรงกับ context ขององค์กร (
urn:logto:organization:<organization_id>) 
# โทเค็นสำหรับองค์กรผิด - คาดหวัง 403
curl -H "Authorization: Bearer token-for-different-organization" \
  http://localhost:3000/api/protected
กรณีทดสอบที่ผสมผสานการตรวจสอบทรัพยากร API กับ context ขององค์กร:
- องค์กร + ขอบเขต API ถูกต้อง: ทดสอบด้วยโทเค็นที่มีทั้ง context ขององค์กรและขอบเขต API ที่ต้องการ
 - ขาดขอบเขต API: คาดหวัง 403 Forbidden เมื่อโทเค็นองค์กรไม่มีสิทธิ์ API ที่จำเป็น
 - องค์กรไม่ถูกต้อง: คาดหวัง 403 Forbidden เมื่อเข้าถึง API ด้วยโทเค็นจากองค์กรอื่น
 - audience ไม่ถูกต้อง: คาดหวัง 403 Forbidden เมื่อ audience ไม่ตรงกับทรัพยากร API ระดับองค์กร
 
# โทเค็นองค์กรที่ไม่มีขอบเขต API - คาดหวัง 403
curl -H "Authorization: Bearer organization-token-without-api-scopes" \
  http://localhost:3000/api/protected
อ่านเพิ่มเติม
RBAC ในทางปฏิบัติ: การนำการอนุญาต (Authorization) ที่ปลอดภัยมาใช้กับแอปพลิเคชันของคุณ
สร้างแอปพลิเคชัน SaaS แบบหลายผู้เช่า: คู่มือฉบับสมบูรณ์ตั้งแต่การออกแบบจนถึงการนำไปใช้