auth

Az auth önálló használata

Teljes, futtatható példa, ami az auth-on kívül semmilyen gp-system függőséget nem igényel: HTTP-vel és anélkül is.

Az előző három oldalon minden működik a gpsystem kit nélkül, server.WriteError nélkül, és bármelyik más gp-system/* modul nélkül. Ez az oldal ennek az állításnak a konkrét változata: egy projekt, aminek a go.mod-ja csak a github.com/gp-system/auth-ot igényli, a saját net/http middleware-ét, saját szerep-ellenőrzését, saját policy-registryjét és saját hibarendelését kézzel drótozva.

go mod init example.com/standalone-demo
go get github.com/gp-system/auth
require github.com/gp-system/auth v0.1.0

Nincs errs, nincs httperr, nincs dbx, semmi más: az auth saját go.mod-ja csak a golang-jwt/jwt/v5-t igényli, és ez az egyetlen dolog, ami a tiedben is landol.

Token kibocsátás és ellenőrzés

package main

import (
    "time"

    "github.com/gp-system/auth"
)

var issuer = auth.NewTokenIssuer[auth.DefaultClaims](auth.Config{
    Secret:    "dev-secret-change-me", // valós kódban: env-ből/secret-kezelőből
    AccessTTL: 15 * time.Minute,
    Issuer:    "standalone-demo",
})

func issueToken(userID, username string, roles, permissions []string) (string, error) {
    claims := auth.NewDefaultClaims(auth.Config{Issuer: "standalone-demo", AccessTTL: 15 * time.Minute},
        userID, username, roles, permissions)
    return issuer.Sign(claims)
}

Kézzel írt net/http middleware

Az Identify végzi a parseolást és az identity-leképzést; te döntöd el, mi történjen sikertelenség esetén. Itt magunk írunk egy kis JSON body-t, errors.Is-szel választva státuszkódot, httperr nélkül:

package main

import (
    "encoding/json"
    "errors"
    "net/http"

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

func requireAuth(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        id, err := auth.Identify(issuer, auth.DefaultIdentity, r.Header.Get("Authorization"))
        if err != nil {
            writeAuthError(w, err)
            return
        }
        next.ServeHTTP(w, r.WithContext(rbac.WithIdentity(r.Context(), id)))
    })
}

func writeAuthError(w http.ResponseWriter, err error) {
    status := http.StatusInternalServerError
    switch {
    case errors.Is(err, auth.ErrMissingToken),
        errors.Is(err, auth.ErrInvalidToken),
        errors.Is(err, auth.ErrInvalidClaims):
        status = http.StatusUnauthorized
    }
    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(status)
    _ = json.NewEncoder(w).Encode(map[string]string{"error": err.Error()})
}

Ez nem az egyetlen lehetséges alak: egy middleware ugyanígy rendere lhet text/plain-t http.Error-ral, vagy újrahasznosíthatja a kódbázisod már meglévő hibakezelési konvencióját. Az auth-nak nincs erről véleménye; csak azt garantálja, hogy az errors.Is működik a három sentinel ellen.

Szerep-ellenőrzés middleware nélkül

Az rbac.CheckRole/CheckPermission sima függvények; közvetlenül hívhatók egy handlerben, guard middleware nélkül is:

func adminOnly(next http.HandlerFunc) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        id := rbac.FromContext(r.Context())
        if err := rbac.CheckRole(id, "admin"); err != nil {
            status := http.StatusForbidden
            if errors.Is(err, rbac.ErrUnauthenticated) {
                status = http.StatusUnauthorized
            }
            http.Error(w, err.Error(), status)
            return
        }
        next(w, r)
    }
}

Fűzd be a requireAuth után:

mux := http.NewServeMux()
mux.Handle("/admin/reports", requireAuth(adminOnly(handleReports)))
http.ListenAndServe(":8080", mux)

Policy-registry, kézzel bekötve

A policy.Enforcer az oapi-codegen strict-server alakját célozza, de semmi nem akadályozza, hogy egy kézzel épített végponthoz közvetlenül egy Registry-t hívj:

package main

import (
    "context"
    "errors"
    "net/http"

    "github.com/gp-system/auth/policy"
    "github.com/gp-system/auth/rbac"
)

var orderPolicies = func() *policy.Registry {
    reg := policy.NewRegistry()
    reg.Register("orders.view", func(_ context.Context, id *rbac.Identity, req any) error {
        orderOwnerID := req.(string) // valós kódban: a rendelés-rekordból kikeresve
        if id.HasRole("admin") || orderOwnerID == id.Subject {
            return nil
        }
        return policy.Deny("you can only view your own orders")
    })
    return reg
}()

func handleGetOrder(w http.ResponseWriter, r *http.Request) {
    id := rbac.FromContext(r.Context())
    fn, _ := orderPolicies.Get("orders.view")
    if err := fn(r.Context(), id, lookupOrderOwner(r)); err != nil {
        var d *policy.Denial
        if errors.As(err, &d) {
            http.Error(w, d.Detail, http.StatusForbidden)
            return
        }
        http.Error(w, "internal error", http.StatusInternalServerError)
        return
    }
    // ... a rendelés kiírása
}

Semmi HTTP: egy WebSocket handshake

Az Identify fejléc-értéket vár, nem *http.Request-et, így ugyanaz a hívás működik bárhol, ahol egy bearer token felbukkan: egy WebSocket handshake Sec-WebSocket-Protocol trükkjében, egy első üzenetben a socketen, gRPC metaadatban, bármiben.

func acceptConnection(authorizationValue string) (*rbac.Identity, error) {
    id, err := auth.Identify(issuer, auth.DefaultIdentity, authorizationValue)
    if err != nil {
        return nil, err // a hívó dönti el, hogyan zárja a kapcsolatot / küldjön control frame-et
    }
    if err := rbac.CheckPermission(id, "realtime.connect"); err != nil {
        return nil, err
    }
    return id, nil
}

Itt nincs mit response-ba írni, tehát nincs mit adaptálni: a fenti HTTP-példák errors.Is/errors.As szókincse változatlanul alkalmazható.

Amit a kit hozzáad

A gpsystem kit server csomagja (server.AuthMiddleware, server.RequireRole/RequirePermission, server.WriteError) pontosan a fenti requireAuth/adminOnly/hibarenderelő kód, egyszer megírva és minden generált projekt között megosztva, RFC 9457 problem+json-ként renderelve az itt bemutatott ad hoc JSON/plain-text helyett. Ehhez a bekötéshez lásd az Autentikáció oldalt. Az auth, az rbac és a policy semmiben nem változik, amikor hozzáadod a kitet: ugyanazokra a sentinelekre és ugyanarra a *policy.Denial-ra illeszkedik a kit glue-ja is.

Copyright © 2026