I am trying to implement google authentication using Go.
The code is working fine if I am running a single service which talks to googleoAuth URL.
But I am facing when I am trying to use HTTP.Redirect.
Basically the idea is on receiving a particular URL the first service should redirect the request to some other service which takes care of Google authentication completely.
I am running both services on my same system on two different ports.
Both the server are running as secured server with self signed certificate.
Here are the codes I am using.
main() function for both servers, only the port numbers are different.
func main(){
//Declare the Mux for the server
fmt.Println("Starting service")
mux := handlers.New()
setupHandlers(mux)
certFile := flag.String("certfile", "../cert/certbundle.pem", "certificate PEM file")
keyFile := flag.String("keyfile", "../cert/server.key", "key PEM file")
flag.Parse()
server := http.Server{
Addr: "127.0.0.1:8080",
Handler: mux,
TLSConfig: &tls.Config{
MinVersion: tls.VersionTLS13,
PreferServerCipherSuites: true,
},
}
server.ListenAndServeTLS(*certFile, *keyFile)
}
Here is the redirection code
func New() *http.ServeMux{
mux := http.NewServeMux()
// Root
//mux.Handle("/", http.FileServer(http.Dir("templates/")))
mux.HandleFunc("/", loginPageHandler)
mux.HandleFunc("/internal", internalPageHandler)
// OauthGoogle
mux.HandleFunc("/auth/google/login", oauthGoogleLogin)
mux.HandleFunc("/auth/google/callback", oauthGoogleCallback)
return mux
}
func loginPageHandler(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, "https://127.0.0.1:8087/", http.StatusMovedPermanently)
}
Here are the codes for handling google authentication
func loginPageHandler(w http.ResponseWriter, r *http.Request) {
tmpl, err := template.ParseFiles("templates/login.html")
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
err = tmpl.Execute(w, nil)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}
func oauthGoogleLogin(w http.ResponseWriter, r *http.Request) {
// Create oauthState cookie
oauthState := generateStateOauthCookie(w)
/*
AuthCodeURL receive state that is a token to protect the user from CSRF attacks. You must always provide a non-empty string and
validate that it matches the state query parameter on your redirect callback.
*/
u := googleOauthConfig.AuthCodeURL(oauthState)
http.Redirect(w, r, u, http.StatusTemporaryRedirect)
}
func generateStateOauthCookie(w http.ResponseWriter) string {
var expiration = time.Now().Add( 1 * time.Hour)
b := make([]byte, 16)
rand.Read(b)
state := base64.URLEncoding.EncodeToString(b)
cookie := http.Cookie{Name: "oauthstate", Value: state, Expires: expiration}
http.SetCookie(w, &cookie)
return state
}
func oauthGoogleCallback(w http.ResponseWriter, r *http.Request) {
// Read oauthState from Cookie
fmt.Println("here")
oauthState, _ := r.Cookie("oauthstate")
if r.FormValue("state") != oauthState.Value {
log.Println("invalid oauth google state")
http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
return
}
data, err := getUserDataFromGoogle(r.FormValue("code"))
if err != nil {
log.Println(err.Error())
http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
return
}
fmt.Fprintf(w, "UserInfo: %s\n", data)
}
There are two issues observed.
First I am getting TLS error from the second service which is the destination of the redirection, http: TLS handshake error from 127.0.0.1:57715: remote error: tls: unknown certificate as soon the request is redirected to the second service.
Secondly, in the func oauthGoogleCallback(), the call r.Cookie("oauthstate") fails with no named cookie found error.
Any thought the reason behind TLS error? I believe if that can be solved then rest will work automatically.
Any help is highly appreciated.
Related
i am building an promethues exporter in golang, the url to the exporter will be http://exporter-ip:9000/unique-id/metrics.
By parsing the url in ProcessParameters() function i am getting unique-id and with unique-id i am getting ip,username,password.
how can i pass IP, Username, Password from ProcessParameters() middleware function to Collect() function.
There variables are request scoped
func (collector *Collector) Collect(ch chan<- prometheus.Metric) {
//need IP,Username & Password here.
}
func ProcessParameters(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
log.Print("Executing middlewareOne")
DeviceID = strings.Split(r.URL.Path, "/")[1] //getting unique ID from the URL and verifying if that id is valid
_, ok := util.Devices[DeviceID] //if device id is not present in map, return StatusForbidden error in if block.
if !ok{
errMsg := "Device not found"
http.Error(w, errMsg, http.StatusForbidden)
log.Println(errMsg)
w.WriteHeader(http.StatusForbidden)
w.Header().Set("Content-Type", "application/json")
resp := make(map[string]string)
resp["message"] = "Forbidden"
jsonResp, err := json.Marshal(resp)
if err != nil {
log.Fatalf("Error happened in JSON marshal. Err: %s", err)
}
w.Write(jsonResp)
} else { //if id is present pass controller final handler(deviceHandler)
// tried setting it to request context also how to access it from collect() func
ctx := context.WithValue(r.Context(), "IP", util.Devices["10.0.0.1"])
context.WithValue(r.Context(), "UserName", util.Devices["user1"])
context.WithValue(r.Context(), "Password", util.Devices["pass1"])
next.ServeHTTP(w, r.WithContext(ctx))
}
})
}
func main() {
collector := metricsCollector()
registry := prometheus.NewRegistry()
registry.Register(collector)
deviceHandler := promhttp.HandlerFor(registry, promhttp.HandlerOpts{})
mux := http.NewServeMux()
mux.Handle("/", ProcessParameters(deviceHandler)) // how can pass variables from ProcessParameters() middleware handler to deviceHandler
err := http.ListenAndServe(":9090", mux)
log.Fatal(err)
}
i'm trying to create an oAuth thanks to the zmb3 library (https://github.com/zmb3/spotify) but i have trouble with it.
i get my url and send it to my front like so, then the refront redirect to the url:
func GetSpotifyUrl(w http.ResponseWriter, r *http.Request) {
fmt.Println("ok")
url := auth.AuthURL(state)
res, _ := json.Marshal(url)
w.WriteHeader(http.StatusOK)
w.Write(res)
}
At this stade, i'm able to press the button "agree" and perform a redirection to my callback function:
func AuthSpotify(w http.ResponseWriter, r *http.Request) {
tok, err := auth.Token(r.Context(), state, r)
if err != nil {
http.Error(w, "Couldn't get token", http.StatusForbidden)
log.Fatal(err)
}
if st := r.FormValue("state"); st != state {
http.NotFound(w, r)
log.Fatalf("State mismatch: %s != %s\n", st, state)
}
client := spotify.New(auth.Client(r.Context(), tok))
// infinite loop on this line ^^^
fmt.Fprintf(w, "Login Completed!")
ch <- client
}
but in this function i have an "infinite loop" at the line where i assign client. I can print the token tok, and the value seems good.
What i'm doing wrong? Thanks a lot
I'm new to golang, and have followed this (https://auth0.com/blog/authentication-in-golang/) auth0 guide, for setting up a go rest api.
I'm struggeling with converting to Fiber, and in the same time putting my functions that are being called by routes, out to seperate files.
Currently my main file looks like this:
func main() {
r := mux.NewRouter()
r.Handle("/", http.FileServer(http.Dir("./views/")))
r.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.Dir("./static/"))))
r.Handle("/posts", config.JwtMiddleware.Handler(GetPosts)).Methods("GET")
//r.Handle("/products/{slug}/feedback", jwtMiddleware.Handler(AddFeedbackHandler)).Methods("POST")
// For dev only - Set up CORS so React client can consume our API
corsWrapper := cors.New(cors.Options{
AllowedMethods: []string{"GET", "POST"},
AllowedHeaders: []string{"Content-Type", "Origin", "Accept", "*"},
})
http.ListenAndServe(":8080", corsWrapper.Handler(r))
}
var GetPosts= http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
collection, err := config.GetMongoDbCollection(dbName, collectionName)
if err != nil {
fmt.Println("Error")
}else{
fmt.Println(collection)
//findOptions := options.Find()
cursor, err := collection.Find(context.Background(), bson.M{})
if err != nil {
log.Fatal(err)
}
var posts[]bson.M
if err = cursor.All(context.Background(), &posts); err != nil {
log.Fatal(err)
}
fmt.Println(posts)
payload, _ := json.Marshal(posts)
w.Header().Set("Content-Type", "application/json")
w.Write([]byte(payload))
}
})
So I would like to convert from: r := mux.NewRouter() to fiber and in the same time move my GetPosts function out in a seperate file. When doing this, I can't seem to continue calling my jwtMiddleware.
I have tried this package: https://github.com/Mechse/fiberauth0 but it seems like its broken. At least I can call protected routes without supplying jwt tokens in my header.
You can simply convert 'net/http' style middleware handlers with the provided adaptor package(https://github.com/gofiber/adaptor). Note you need to make some changes to the function signature provided by auth0 but this works;
// EnsureValidToken is a middleware that will check the validity of our JWT.
func ensureValidToken(next http.Handler) http.Handler {
issuerURL, err := url.Parse("https://" + os.Getenv("AUTH0_DOMAIN") + "/")
if err != nil {
log.Fatalf("Failed to parse the issuer url: %v", err)
}
provider := jwks.NewCachingProvider(issuerURL, 5*time.Minute)
jwtValidator, err := validator.New(
provider.KeyFunc,
validator.RS256,
issuerURL.String(),
[]string{os.Getenv("AUTH0_AUDIENCE")},
validator.WithCustomClaims(
func() validator.CustomClaims {
return &CustomClaims{}
},
),
validator.WithAllowedClockSkew(time.Minute),
)
if err != nil {
log.Fatalf("Failed to set up the jwt validator")
}
errorHandler := func(w http.ResponseWriter, r *http.Request, err error) {
log.Printf("Encountered error while validating JWT: %v", err)
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusUnauthorized)
w.Write([]byte(`{"message":"Failed to validate JWT."}`))
}
middleware := jwtmiddleware.New(
jwtValidator.ValidateToken,
jwtmiddleware.WithErrorHandler(errorHandler),
)
return middleware.CheckJWT(next)
}
var EnsureValidToken = adaptor.HTTPMiddleware(ensureValidToken)
app := fiber.New()
app.Use(EnsureValidToken)
app.Get("/", func(c *fiber.Ctx) error {
return c.SendString("Hello, World!")
})
app.Listen(":3000")
I want to secure Docker daemon REST API using Go reverse proxy server. I found this article very relevant. I have never used Go so not sure how to implement basic authentication to this with static username and password. I tried all possible ways i happened to find over Google but none worked for me.
Could some please help adding static basicAuth authentication to following code so that request so that Docker daemon API is only reachable if the request includes username and password:
https://github.com/ben-lab/blog-material/blob/master/golang-reverse-proxy-2/reverse-proxy.go
package main
import (
"fmt"
"io"
"log"
"net/http"
"time"
"github.com/tv42/httpunix"
)
func handleHTTP(w http.ResponseWriter, req *http.Request) {
fmt.Printf("Requested : %s\n", req.URL.Path)
u := &httpunix.Transport{
DialTimeout: 100 * time.Millisecond,
RequestTimeout: 1 * time.Second,
ResponseHeaderTimeout: 1 * time.Second,
}
u.RegisterLocation("docker-socket", "/var/run/docker.sock")
req.URL.Scheme = "http+unix"
req.URL.Host = "docker-socket"
resp, err := u.RoundTrip(req)
if err != nil {
http.Error(w, err.Error(), http.StatusServiceUnavailable)
return
}
defer resp.Body.Close()
copyHeader(w.Header(), resp.Header)
w.WriteHeader(resp.StatusCode)
io.Copy(w, resp.Body)
}
func copyHeader(dst, src http.Header) {
for k, vv := range src {
for _, v := range vv {
dst.Add(k, v)
}
}
}
func main() {
server := &http.Server{
Addr: ":8888",
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { handleHTTP(w, r) }),
}
log.Fatal(server.ListenAndServe())
}
https://github.com/ben-lab/blog-material/blob/master/golang-reverse-proxy-2/reverse-proxy.go
You can access the basic auth header values by calling BasicAuth() on your
req *http.Request object
like:
user, pass, _ := req.BasicAuth()
Then compare user and pass with the static values you have.
https://golang.org/pkg/net/http/#Request.BasicAuth
Update:
func handleHTTP(w http.ResponseWriter, req *http.Request) {
user, pass, _ := req.BasicAuth()
if user != "muuser" || pass != "mysecret" {
// you have to import "errors"
http.Error(w, errors.New("not authoized!!"), http. StatusUnauthorized)
return
}
fmt.Printf("Requested : %s\n", req.URL.Path)
u := &httpunix.Transport{
DialTimeout: 100 * time.Millisecond,
RequestTimeout: 1 * time.Second,
ResponseHeaderTimeout: 1 * time.Second,
}
u.RegisterLocation("docker-socket", "/var/run/docker.sock")
req.URL.Scheme = "http+unix"
req.URL.Host = "docker-socket"
resp, err := u.RoundTrip(req)
if err != nil {
http.Error(w, err.Error(), http.StatusServiceUnavailable)
return
}
defer resp.Body.Close()
copyHeader(w.Header(), resp.Header)
w.WriteHeader(resp.StatusCode)
io.Copy(w, resp.Body)
}
Here you are, you can copy the logic from my following little project.
https://github.com/alessiosavi/StreamingServer/blob/0f65dbfc77f667777d3047fa1a6b1a2cbd8aaf26/auth/authutils.go
In first instance you need a server for store the users (I've used Redis).
Than you need 3 function for the user
LoginUser
RegisterUser
DeleteUser
During the login/register phase, you generate a cookie hashing username/password and setting the cookie into a Redis table
Than you verify every time that an API is called.
Feel free to copy the code that you need.
Open an issue if something is not well understandable.
I have been implementing server using golang. I need to shutdown my server after receiving the expected parameter 'code'. Before shutting down the sever I need to redirect to a another web page. I have implemented as give below. This code is working. I need to know whether it is the best way of doing it ? Your suggestions are appreciated..
func main() {
var code string
const port int = 8888
httpPortString := ":" + strconv.Itoa(port)
mux := http.NewServeMux()
fmt.Printf("Http Server initialized on Port %s", httpPortString)
server := http.Server{Addr: httpPortString, Handler: mux}
var timer *time.Timer
mux.HandleFunc("/auth", func(w http.ResponseWriter, r *http.Request) {
err := r.ParseForm()
if err != nil {
fmt.Printf("Error parsing the code: %s", err)
}
code = r.Form.Get("code")
if err != nil {
log.Printf("Error occurred while establishing the server: %s", err)
}
http.Redirect(w, r, "https://cloud.google.com/sdk/auth_success", http.StatusMovedPermanently)
timer = time.NewTimer(2 * time.Second)
go func() {
<-timer.C
server.Shutdown(context.Background())
}()
})
if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
fmt.Printf("Error while establishing the service: %s", err)
}
fmt.Println("Finished executing the the service")
}
Thank you ..!
Taking #Peter flushing suggestion and ideas from the example cited here:
f, ok := w.(http.Flusher)
if !ok {
http.Error(w, "no flush support", http.StatusInternalServerError)
return
}
http.Redirect(w, r, "https://cloud.google.com/sdk/auth_success", http.StatusSeeOther)
f.Flush() // <-- ensures client gets all writes
// this is done implicitly on http handler returns, but...
// we're shutting down the server now!
go func() {
server.Shutdown(context.Background())
close(idleConnsClosed)
}()
See full playground version for idleConnsClosed setup/cleanup: https://play.golang.org/p/UBmLfyhKT0B
P.S. Don't use http.StatusMovedPermanently unless you really want users never to use the source URL again. Users' browsers will cache this (301) code - and not hit your server - which may not be what you want. If you want temporary redirects use http.StatusSeeOther (code 303).