getting-started/authenticating-users/main.go (113 lines of code) (raw):

// Copyright 2019 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // https://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // [START getting_started_auth_setup] // The authenticating-users program is a sample web server application that // extracts and verifies user identity data passed to it via Identity-Aware // Proxy. package main import ( "encoding/json" "fmt" "log" "net/http" "os" "time" "cloud.google.com/go/compute/metadata" "github.com/golang-jwt/jwt" ) // app holds the Cloud IAP certificates and audience field for this app, which // are needed to verify authentication headers set by Cloud IAP. type app struct { certs map[string]string aud string } func main() { a, err := newApp() if err != nil { log.Fatal(err) } http.HandleFunc("/", a.index) port := os.Getenv("PORT") if port == "" { port = "8080" log.Printf("Defaulting to port %s", port) } log.Printf("Listening on port %s", port) if err := http.ListenAndServe(":"+port, nil); err != nil { log.Fatal(err) } } // newApp creates a new app, returning an error if either the Cloud IAP // certificates or the app's audience field cannot be obtained. func newApp() (*app, error) { certs, err := certificates() if err != nil { return nil, err } aud, err := audience() if err != nil { return nil, err } a := &app{ certs: certs, aud: aud, } return a, nil } // [END getting_started_auth_setup] // [START getting_started_auth_front_controller] // index responds to requests with our greeting. func (a *app) index(w http.ResponseWriter, r *http.Request) { if r.URL.Path != "/" { http.NotFound(w, r) return } assertion := r.Header.Get("X-Goog-IAP-JWT-Assertion") if assertion == "" { fmt.Fprintln(w, "No Cloud IAP header found.") return } email, _, err := validateAssertion(assertion, a.certs, a.aud) if err != nil { log.Println(err) fmt.Fprintln(w, "Could not validate assertion. Check app logs.") return } fmt.Fprintf(w, "Hello %s\n", email) } // [END getting_started_auth_front_controller] // [START getting_started_auth_validate] // validateAssertion validates assertion was signed by Google and returns the // associated email and userID. func validateAssertion(assertion string, certs map[string]string, aud string) (email string, userID string, err error) { token, err := jwt.Parse(assertion, func(token *jwt.Token) (interface{}, error) { keyID := token.Header["kid"].(string) _, ok := token.Method.(*jwt.SigningMethodECDSA) if !ok { return nil, fmt.Errorf("unexpected signing method: %q", token.Header["alg"]) } cert := certs[keyID] return jwt.ParseECPublicKeyFromPEM([]byte(cert)) }) if err != nil { return "", "", err } claims, ok := token.Claims.(jwt.MapClaims) if !ok { return "", "", fmt.Errorf("could not extract claims (%T): %+v", token.Claims, token.Claims) } if claims["aud"].(string) != aud { return "", "", fmt.Errorf("mismatched audience. aud field %q does not match %q", claims["aud"], aud) } return claims["email"].(string), claims["sub"].(string), nil } // [END getting_started_auth_validate] // [START getting_started_auth_audience] // audience returns the expected audience value for this service. func audience() (string, error) { projectNumber, err := metadata.NumericProjectID() if err != nil { return "", fmt.Errorf("metadata.NumericProjectID: %w", err) } projectID, err := metadata.ProjectID() if err != nil { return "", fmt.Errorf("metadata.ProjectID: %w", err) } return "/projects/" + projectNumber + "/apps/" + projectID, nil } // [END getting_started_auth_audience] // [START getting_started_auth_certs] // certificates returns Cloud IAP's cryptographic public keys. func certificates() (map[string]string, error) { const url = "https://www.gstatic.com/iap/verify/public_key" client := http.Client{ Timeout: 5 * time.Second, } resp, err := client.Get(url) if err != nil { return nil, fmt.Errorf("Get: %w", err) } var certs map[string]string dec := json.NewDecoder(resp.Body) if err := dec.Decode(&certs); err != nil { return nil, fmt.Errorf("Decode: %w", err) } return certs, nil } // [END getting_started_auth_certs]