Go net/http-Sitzungen
Das Paket github.com/keenauth/keenauth-go/bff fügt einer Go-net/http-Anwendung die Anmeldung hinzu. Ihr Server ist der OAuth-Client und bewahrt alle Token auf dem Server auf. Der Browser erhält nur ein verschlüsseltes HttpOnly-Sitzungs-Cookie und sieht nie ein Access-, Refresh- oder ID-Token. Dies ist das Backend-for-Frontend-(BFF)-Muster. Das Paket verwendet nur die Standardbibliothek. Es ist noch nicht veröffentlicht.
Um stattdessen eine API zu schützen, die Bearer-Token empfängt, siehe Ihre API schützen.
Bevor Sie beginnen
- Erstellen Sie im Dashboard eine Web-Anwendung in Ihrer Umgebung.
- Wählen Sie die Client-Authentifizierung: ein Client-Secret (
bff.ClientSecretBasic) oderprivate_key_jwt(bff.PrivateKeyJWT). Beiprivate_key_jwtregistrieren Sie den öffentlichen Schlüssel der Anwendung und bewahren den privaten Schlüssel auf Ihrem Server auf. - Fügen Sie die Redirect-URI
https://app.acme.example/auth/callbackund die Redirect-URI nach dem Abmeldenhttps://app.acme.example/hinzu. - Erzeugen Sie ein Cookie-Secret mit mindestens 32 Zeichen, zum Beispiel mit
openssl rand -base64 32. Bewahren Sie es in Ihrem Secret-Store auf, nicht im Repository.
Einrichtung
package main
import (
"fmt"
"net/http"
"os"
"github.com/keenauth/keenauth-go/bff"
)
func main() {
auth, err := bff.New(bff.Options{
Issuer: "https://acme.eu.keenauth.app", // your environment's issuer, exactly
ClientID: "app_01j9zzzzzzzzzzzzzzzzzzzz",
ClientAuth: bff.ClientSecretBasic{Secret: os.Getenv("KEENAUTH_CLIENT_SECRET")},
AppBaseURL: "https://app.acme.example",
Secrets: []string{os.Getenv("KEENAUTH_COOKIE_SECRET")},
})
if err != nil {
panic(err) // configuration errors stop the app at start-up
}
mux := http.NewServeMux()
mux.Handle("GET /orders", auth.RequireAuth(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
s := bff.FromContext(r.Context()).Session
fmt.Fprintf(w, "Signed in as %s", s.User.Email)
})))
http.ListenAndServe(":8080", auth.Middleware(mux))
}
Um stattdessen private_key_jwt statt eines Client-Secrets zu verwenden, übergeben Sie den privaten Schlüssel. Das Paket signiert für jede Token- und PAR-Anfrage eine neue Client-Assertion (iss und sub sind Ihre Client-ID, aud ist der Issuer, Lebensdauer 2 Minuten, bei jeder Anfrage ein neues jti):
pemBytes, err := os.ReadFile("/run/secrets/keenauth-app-key.pem")
if err != nil {
panic(err)
}
key, err := assertion.ParsePrivateKeyPEM(pemBytes) // import "github.com/keenauth/keenauth-go/assertion"
if err != nil {
panic(err)
}
auth, err := bff.New(bff.Options{
Issuer: "https://acme.eu.keenauth.app",
ClientID: "app_01j9zzzzzzzzzzzzzzzzzzzz",
ClientAuth: bff.PrivateKeyJWT{Key: key, KeyID: "app-key-2026-11"},
AppBaseURL: "https://app.acme.example",
Secrets: []string{os.Getenv("KEENAUTH_COOKIE_SECRET")},
})
ES256 (P-256), EdDSA (Ed25519), RS256 und PS256 (RSA, 2048 Bit oder mehr) werden unterstützt; der Algorithmus richtet sich nach dem Schlüssel, und Algorithm: "PS256" wählt PSS für einen RSA-Schlüssel aus. Key kann jeder crypto.Signer sein, sodass auch ein in einem KMS oder HSM gehaltener Schlüssel funktioniert. Wenn Sie anderswo signieren, setzen Sie stattdessen bff.PrivateKeyJWT{Assertion: f}, wobei f bei jedem Aufruf eine neu signierte Assertion zurückgibt. Setzen Sie entweder Key oder Assertion, nicht beides.
AppBaseURL ist die öffentliche Adresse Ihrer App. Das Paket erstellt daraus die Redirect-URI und alle Weiterleitungen, niemals aus dem Host- oder X-Forwarded-*-Header der Anfrage. Unverschlüsseltes http wird nur für localhost akzeptiert.
Routen
Middleware stellt drei Routen bereit und liest bei jeder anderen Anfrage die Sitzung. Sie können die Routen auch allein einbinden mit mux.Handle("/auth/", auth).
| Route | Beschreibung |
|---|---|
GET /auth/login |
Startet die Anmeldung. Optionale Query-Parameter: returnTo (wohin der Benutzer gelangt) und organization (Anmeldung bei einer Organisation). |
GET /auth/callback |
Schließt die Anmeldung ab und setzt das Session-Cookie. |
GET oder POST /auth/logout |
Meldet bei KeenAuth ab und kehrt zu Ihrer App zurück. |
Die Anmeldung verwendet den Authorization-Code-Flow mit PKCE (S256). Die Anfrage wird an den Server übermittelt (PAR), sofern Ihre Umgebung dies anbietet. Beim Callback prüft das Paket state, iss und das ID-Token einschließlich seines nonce. Jede erfolgreiche Anmeldung erzeugt eine neue Sitzung; die alte wird verworfen.
returnTo muss ein Pfad Ihrer eigenen App sein, etwa /orders?tab=open. Jeder andere Wert, etwa //evil.example oder eine vollständige URL, wird ignoriert, und der Benutzer gelangt auf /.
Handler schützen
auth.RequireAuth(handler) lässt eine Anfrage nur mit einer Sitzung durch:
- eine Seitenanfrage (
GEToderHEAD) ohne Sitzung wird mitreturnToauf/auth/loginumgeleitet; - eine Anfrage an
/api/…oder mit einer anderen Methode erhält401mit einem Problem-Dokument. Übergeben Siebff.GuardOptions{API: true}, um auf jedem Pfad mit401zu antworten.
Bei POST, PUT, PATCH, DELETE und anderen unsicheren Methoden muss der Browser angeben, dass die Anfrage von Ihrer App stammt: Sec-Fetch-Site: same-origin oder ein Origin-Header, der dem Ursprung Ihrer App entspricht. Andere Anfragen erhalten 403. Dies schützt zusätzlich zu SameSite=Lax vor Cross-Site-Request-Forgery.
Sitzung verwenden und Ihre API aufrufen
bff.FromContext(r.Context()) liefert:
Session:niloder den angemeldeten Benutzer.Session.Userenthält typisierte ID-Token-Claims wieSubject,Email,Name,OrgIDundOrgRoles; alle Claims stehen inSession.User.Claims. Die Sitzung enthält niemals ein Token.AccessToken: ein gültiges Access-Token für serverseitige Aufrufe oder"". Senden Sie es nicht an den Browser.
Läuft das Access-Token innerhalb von 60 Sekunden ab, erneuert das Paket es, bevor Ihr Handler ausgeführt wird. Jede Erneuerung rotiert das Refresh-Token und setzt das neue Session-Cookie in der Antwort. Verweigert der Server das Refresh-Token, endet die Sitzung. Ein vorübergehender Serverfehler lässt die Sitzung unverändert. Gleichzeitige Anfragen in einem Prozess teilen sich eine Erneuerung.
Organisation wechseln
auth.SwitchOrganizationPath("org_01j9…", "/dashboard") gibt einen Link zu /auth/login zurück, der den Benutzer erneut in dieser Organisation anmeldet. KeenAuth prüft die Mitgliedschaft. Nach der Anmeldung enthält Session.User.OrgID die Organisation.
Abmelden
Senden Sie aus einem Formular ein POST an /auth/logout. Das Paket löscht das Session-Cookie (und die gespeicherte Sitzung, falls Sie einen Store verwenden), widerruft das Refresh-Token und leitet den Browser zum Logout-Endpunkt Ihrer Umgebung weiter, der ihn zu Ihrer App zurückbringt. Eine Anfrage, die der Browser als von einer anderen Website stammend kennzeichnet, wird abgelehnt. Browser, die kein Sec-Fetch-Site senden, können sich nur mit einem POST von Ihren eigenen Seiten abmelden.
Sitzungsspeicher
Standardmäßig befindet sich die gesamte Sitzung im Cookie, verschlüsselt und mit AES-256-GCM authentifiziert, mit einem aus dem Secret abgeleiteten Schlüssel. Ein verändertes oder kopiertes Cookie wird abgelehnt. Das Cookie heißt __Host-keenauth_session.0 und wird mit Secure, HttpOnly, SameSite=Lax und Path=/ gesetzt. Auf http://localhost entfallen das Präfix __Host- und Secure. Große Sitzungen werden auf bis zu drei Cookies aufgeteilt.
Um Token auf dem Server zu halten, setzen Sie Options.Store auf ein bff.SessionStore (Get, Set, Delete), zum Beispiel mit Redis als Backend. Das Cookie enthält dann nur eine verschlüsselte zufällige Sitzungs-ID. Jede Anmeldung erzeugt eine neue ID und löscht den vorherigen Datensatz. Der Store enthält Token, daher beschränken Sie den Zugriff darauf. bff.MemoryStore ist nur für Entwicklung und Tests gedacht.
Eine Sitzung dauert höchstens 7 Tage (SessionMaxAge); eine Erneuerung verlängert sie nicht. Um das Secret zu ändern, tragen Sie das neue zuerst ein: Secrets: []string{newSecret, oldSecret}. Entfernen Sie das alte nach SessionMaxAge.
Aktuelle Einschränkungen
- DPoP-gebundene Token und Back-Channel-Logout werden noch nicht unterstützt.
- Erneuerungen werden nur innerhalb eines Serverprozesses geteilt. Bei mehreren Instanzen lässt die Anwendungseinstellung
refresh_reuse_interval_s(Standard 5 Sekunden) eine parallele Erneuerung erfolgreich sein; setzen Sie sie auf0, beendet die zweite Erneuerung die Sitzung.