auth

RBAC

Szerep- és permission-ellenőrzések a contextben utazó Identity fölött: route-guardokkal és service-rétegbeli lekérdezéssel.

Importáld az rbac alcsomagot, hogy szerepeket és jogosultságokat ellenőrizz az autentikált identity fölött:

import "github.com/gp-system/auth/rbac"

Az rbac az auth modul szerep- és jogosultság-ellenőrző alcsomagja: szerep- és jogosultság-ellenőrzéseket ad egy autentikált Identity fölött, ami a request contextben utazik. Nem foglal állást arról, honnan jönnek a szerepek: az auth.Identify (vagy bármi más) tölti be az Identity-t, az rbac csak ellenőriz: adatbázis-sémát, user-táblát nem feltételez, és sosem ír választ.

A csomag három szintet fed le: id.HasRole("admin") / id.HasPermission("orders.view") a legegyszerűbb bool-ellenőrzésekhez, rbac.CheckRole(id, "admin") / rbac.CheckPermission(id, "orders.view"), ami bool helyett ErrUnauthenticated/ErrForbidden-t ad vissza (akkor hasznos, ha külön nil-ellenőrzés nélkül meg kell különböztetned a "nincs identity"-t a "rossz szerep"-től), és a gpsystem kit server.RequireRole("admin") / server.RequirePermission("orders.view")-ja a route-szintű net/http middleware-védelemhez, ami ezt a két sentinelt Problemként rendereli. Egy kit nélküli, csak CheckRole-ra épülő, kézzel írt middleware-hez lásd az Önálló használat oldalt.

Az Identity

Az Identity a hívó, ahogy az authorizációs döntések és az üzleti logika látja: ez a kontraktus az autentikáció és az authorizáció között:

type Identity struct {
    Subject     string         // a user azonosítója (a JWT sub claimje)
    Username    string
    Roles       []string
    Permissions []string
    Extra       map[string]any // a standard mezőkön túli claim-adat (pl. tenant)
}

Két lekérdező metódusa van, mindkettő any-of szemantikával: több argumentumból egy találat is elég.

id.HasRole("admin", "editor")       // igaz, ha bármelyik szerep megvan
id.HasPermission("orders.view")     // igaz, ha a permission megvan

Mindkettő biztonságosan hívható nil identity-n is (ilyenkor false-t ad); ez illeszkedik ahhoz, hogy a FromContext nil-t ad vissza, ha a kérés nem ment át auth middleware-en. Így a guard-kódodban nem kell nil-ellenőrzést írnod:

if rbac.FromContext(ctx).HasRole("admin") { // nil-en is működik, false-t ad
    // ...
}

WithIdentity és FromContext

Az Identity sima context.Context-ben utazik:

ctx = rbac.WithIdentity(ctx, id) // az auth middleware hívja
id := rbac.FromContext(ctx)      // *rbac.Identity, vagy nil auth nélkül

Az Identity, a WithIdentity és a FromContext framework-független: se net/http-importja, se router-importja nincs. Ezért ugyanez a kontraktus használható a HTTP-rétegen kívül is: service-ekben, listenerekben és jobokban, ha a contextbe identity került.

A shopban például a rendeléslistázás service-szinten szűkül a hívóra: az admin mindent lát, a vásárló csak a sajátját.

// internal/modules/shop/surfaces/api/service/orders.go
func (s *Service) ListOrders(ctx context.Context, cursor string) ([]Order, error) {
    id := rbac.FromContext(ctx)
    if id.HasRole("admin") {
        return s.orders.ListAll(ctx, cursor)
    }
    return s.orders.ListByUser(ctx, id.Subject, cursor)
}

Ez adat-szűkítés (mit lásson), nem hozzáférésdöntés (szabad-e egyáltalán). A kérésenkénti engedélyezés, azaz hogy „megnézheti-e ezt a konkrét rendelést", a policy-k dolga.

Route-guardok

A kit server.RequireRole / server.RequirePermission middleware-ei egy egész route-csoportot védenek. Feltételezik, hogy előttük már futott a server.AuthMiddleware; ők csak a contextbeli Identity-t vizsgálják, az rbac.CheckRole/CheckPermission-en keresztül, és az eredményt Problemként rendereli.

router.Route("/admin/shop", func(r chi.Router) {
    r.Use(
        server.AuthMiddleware(deps.Auth), // Bearer → parse → Identity a contextbe
        server.RequireRole("admin"),      // identity nélkül 401, szerep nélkül 403
    )
    // ...
})

// permission-alapú változat, any-of szemantikával:
router.Route("/reports", func(r chi.Router) {
    r.Use(server.AuthMiddleware(deps.Auth),
        server.RequirePermission("reports.view", "reports.export"))
    // ...
})

A válaszok RFC 9457 problem dokumentumok:

HelyzetEredmény
nincs Identity a contextben (nem futott auth)401 authentication required
van identity, de egyik felsorolt szerep/permission sincs meg403 insufficient privileges
bármelyik felsorolt szerep/permission megvan (any-of)átengedés

Ez alatt a server.RequireRole/RequirePermission közvetlenül az rbac.CheckRole/CheckPermission-t hívja, és az rbac.ErrUnauthenticated/ErrForbidden-t képezi le erre a két státuszra; egy kit nélküli hívó ugyanezt a leképzést kézzel végzi el, ahogy az Önálló használat oldal mutatja.

A kittel

A shopban a teljes admin surface (termék CRUD, képfeltöltés) a fenti párossal van védve: az add surface által generált RegisterAdmin függvénybe kerül a middleware, így a védelem surface-szintű, és a testvér api surface-t nem érinti. A pontos bekötést (és a deps.Auth mező felvételét) az autentikáció oldal mutatja; a surface-per-függvény szerkezetről az add surface szól.

Ugyanaz az Identity a HTTP kérés/válasz-cikluson kívül is felbukkan: a realtime gateway minden WebSocket-kapcsolat JWT-jét Identity-vé oldja fel, és a topicra regisztrált Allow/AllowPrefix callbackek ellen ellenőrzi, így egy kapcsolat csak olyan csatornákra iratkozhat fel, amelyekre a hívó jogosult.

Finomabb szemcséhez ugyanezek a guardok route-csoport helyett kisebb csoportra is tehetők, de ha a döntés már nem „van-e ilyen szerepe", hanem „az övé-e ez az erőforrás", az nem RBAC: arra a policy-réteg való.

Honnan jönnek a szerepek és a permissionök

A kit alapfelállásában a JWT claimjeiből: a login végpont a DefaultClaims-be írja a user szerepeit és permissionjeit, az auth middleware pedig ezekből tölti az Identity mezőit. A teljes lánc az autentikáció oldalon. Mivel az rbac csak az Identity-t látja, a forrás lecserélhető: saját claims-típus (Identify/server.AuthMiddlewareFor), API-kulcsból épített identity vagy teszt-fixture ugyanúgy működik.

Teszthez nem kell HTTP: a rbac.WithIdentity-vel közvetlenül a contextbe teheted a kívánt identityt, és a service-réteg úgy viselkedik, mintha a middleware töltötte volna be.
ctx := rbac.WithIdentity(t.Context(), &rbac.Identity{
    Subject: "u-42", Roles: []string{"admin"},
})

A szerep azt mondja meg, a hívó általában mit tehet. Ha a döntéshez a konkrét kérés is kell (tulajdonos-e, egy tenantban vannak-e, jó állapotban van-e a rekord), lépj tovább a policy-kra.

Használt patternek

  • Context-injektált identity (WithIdentity/FromContext, unexported kulccsal, nil-safe metódusok): Design patternek.
  • Magasabb-rendű authorizációs guard (requireFn, amit a RequireRole és a RequirePermission is megoszt): Design patternek.
Copyright © 2026