diff --git a/main.go b/main.go new file mode 100644 index 0000000..53fa62d --- /dev/null +++ b/main.go @@ -0,0 +1,196 @@ +package main + +import ( + "context" + "crypto/rand" + "encoding/base64" + "fmt" + "log" + "net/http" + + "github.com/coreos/go-oidc/v3/oidc" + "golang.org/x/oauth2" +) + +const ( + keycloakURL = "https://keycloak.example.com/realms/myrealm" + + clientID = "go-demo" + clientSecret = "CHANGE_ME" + + redirectURL = "https://app.example.com/callback" +) + +var ( + oauthConfig *oauth2.Config + verifier *oidc.IDTokenVerifier +) + +func main() { + + ctx := context.Background() + + // OIDC Discovery (.well-known/openid-configuration) + provider, err := oidc.NewProvider(ctx, keycloakURL) + if err != nil { + log.Fatal(err) + } + + verifier = provider.Verifier( + &oidc.Config{ + ClientID: clientID, + }, + ) + + oauthConfig = &oauth2.Config{ + ClientID: clientID, + ClientSecret: clientSecret, + Endpoint: provider.Endpoint(), + RedirectURL: redirectURL, + Scopes: []string{ + "openid", + "profile", + "email", + }, + } + + http.HandleFunc("/", home) + http.HandleFunc("/login", login) + http.HandleFunc("/callback", callback) + http.HandleFunc("/logout", logout) + + log.Println("running :8080") + log.Fatal(http.ListenAndServe(":8080", nil)) +} + + +// Startet OIDC Login +func login(w http.ResponseWriter, r *http.Request) { + + // CSRF-Schutz: State erzeugen + state := randomString() + + http.SetCookie(w, &http.Cookie{ + Name: "oidc_state", + Value: state, + Path: "/", + }) + + url := oauthConfig.AuthCodeURL( + state, + ) + + http.Redirect(w, r, url, http.StatusFound) +} + + +// Callback von Keycloak +func callback(w http.ResponseWriter, r *http.Request) { + + stateCookie, _ := r.Cookie("oidc_state") + + if stateCookie == nil || + stateCookie.Value != r.URL.Query().Get("state") { + http.Error(w, "invalid state", 400) + return + } + + + token, err := oauthConfig.Exchange( + r.Context(), + r.URL.Query().Get("code"), + ) + + if err != nil { + http.Error(w, err.Error(), 500) + return + } + + + rawIDToken, ok := token.Extra("id_token").(string) + + if !ok { + http.Error(w, "missing id_token", 500) + return + } + + + idToken, err := verifier.Verify( + r.Context(), + rawIDToken, + ) + + if err != nil { + http.Error(w, "invalid token", 500) + return + } + + + // Userinformationen aus Token lesen + var claims struct { + Username string `json:"preferred_username"` + Email string `json:"email"` + } + + idToken.Claims(&claims) + + + http.SetCookie(w, &http.Cookie{ + Name: "user", + Value: claims.Username, + Path: "/", + }) + + + http.Redirect(w, r, "/", 302) +} + + +func home(w http.ResponseWriter, r *http.Request) { + + cookie, err := r.Cookie("user") + + if err != nil { + + fmt.Fprint(w, ` +