package main import ( "context" "crypto/rand" "encoding/base64" "fmt" "time" "log" "net/http" "crypto/tls" "github.com/coreos/go-oidc/v3/oidc" "golang.org/x/oauth2" ) const ( keycloakURL = "https://auth.cloud.osnawiki.de/realms/oidc-demo" clientID = "go-demo" clientSecret = "VEnWQP1oWCLVCnXB425OxihRj52wfpsisDz1krZlvnSsTpG8GL4v1egffHGDAvhOjZHCFQ4jd2OJ3rHfX2Ge3C" redirectURL = "https://oidc-demo.cloud.osnawiki.de/callback" ) var ( oauthConfig *oauth2.Config verifier *oidc.IDTokenVerifier sessionManager *scs.SessionManager ) func main() { http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{ InsecureSkipVerify: true, } ctx := context.Background() // valkey session client, err := valkey.NewClient( valkey.ClientOption{ InitAddress: []string{ "valkey:6379", }, }, ) if err != nil { log.Fatal(err) } sessionManager = scs.New() sessionManager.Store = valkeystore.New(client) sessionManager.Lifetime = 24 * time.Hour sessionManager.Cookie.HttpOnly = true sessionManager.Cookie.Secure = true sessionManager.Cookie.SameSite = http.SameSiteLaxMode // valkey session end // 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", sessionManager.LoadAndSave(http.DefaultServeMux), ), ) } // Startet OIDC Login func login(w http.ResponseWriter, r *http.Request) { // CSRF-Schutz: State erzeugen state := randomString() sessionManager.Put( r.Context(), "user", claims.Username, ) 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) { user := sessionManager.GetString( r.Context(), "user", ) if user == "" { fmt.Fprint(w, `

OIDC Demo

`) return } fmt.Fprintf( w, `

Hallo %s

`, user, ) } func logout(w http.ResponseWriter, r *http.Request) { sessionManager.Destroy( r.Context(), ) http.Redirect( w, r, "/", http.StatusFound, ) } // kleiner State Generator func randomString() string { b := make([]byte, 16) rand.Read(b) return base64.RawURLEncoding.EncodeToString(b) }