Session middleware for Go? - session

func indexHandler(w http.ResponseWriter, req *http.Request) {
session, err := store.Get(req, sessionName)
if err != nil {
log.WithError(err).Error("bad session")
http.SetCookie(w, &http.Cookie{Name: sessionName, MaxAge: -1, Path: "/"})
}
err = views.ExecuteTemplate(w, "index.html", session.Values)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}
All my handlers use Gorilla sessions. How can I avoid store.Getting the session in each of my handlers, i.e. code repeating?
Another issue, is there a better way to give the template the session values, other that an explicit way like:
err = views.ExecuteTemplate(w, "index.html",
struct {
Session map[interface{}]interface{},
...other handler specific data for the template
}{
session.Values,
...
})
Code samples originate from https://github.com/kaihendry/internal-google-login

For the middleware you can define a function that takes a handler as an argument and returns another handler as its result.
func sessionMiddleware(h http.HandlerFunc) http.HandlerFunc {
// ...
}
The returned handler can store.Get the session, if it's not present return the error, if it is present store the session into the request's context and then call the actual handler.
func sessionMiddleware(h http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
session, err := store.Get(r, sessionName)
if err != nil {
log.WithError(err).Error("bad session")
http.SetCookie(w, &http.Cookie{Name: sessionName, MaxAge: -1, Path: "/"})
return
}
r = r.WithContext(context.WithValue(r.Context(), "session", session))
h(w, r)
}
}
Now your handlers will still need to "get" the session value from the context however any handler that is wrapped by sessionMiddleware can assume, when it's executed, that a session is present in the context and therefore it can skip the error checking.
func indexHandler(w http.ResponseWriter, req *http.Request) {
session := req.Context().Value("session").(*sessions.Session)
err := views.ExecuteTemplate(w, "index.html", session.Values)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}
And to register the handler you would do:
app.HandleFunc("/", sessionMiddleware(indexHandler))
If this is still too much code repetition for your taste you can pass the session directly to your handlers however you'll have to change their signature.
type SessionHandler func(w http.ResponseWriter, r *http.Request, s *session.Session)
Then update your handlers.
func indexHandler(w http.ResponseWriter, req *http.Request, s *session.Session) {
err := views.ExecuteTemplate(w, "index.html", s.Values)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}
And you can define the middleware part on the SessionHandler type as a method.
func (h SessionHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
session, err := store.Get(r, sessionName)
if err != nil {
log.WithError(err).Error("bad session")
http.SetCookie(w, &http.Cookie{Name: sessionName, MaxAge: -1, Path: "/"})
return
}
h(w, r, session)
}
And then to register the handler you would do:
app.Handle("/", SessionHandler(indexHandler))

Related

Golang Fiber and Auth0

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")

Gorm and go-chi REST patch resource

I am building a REST API using chi and gorm
I want to have a patch route where I can update only the properties I receive in the request body.
I am not sure how is the best way of passing there properties to gorm update method.
Which would be a good way of doing so?
Here is the handler method.
func (m Env) UpdateHandler(w http.ResponseWriter, r *http.Request) {
var delta InsurancePolicy
insurancePolicy := r.Context().Value("insurancePolicy").(InsurancePolicy)
err := json.NewDecoder(r.Body).Decode(&delta)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
return
}
result := m.DB.Model(&insurancePolicy).Update(delta)
if result.Error != nil {
w.WriteHeader(http.StatusInternalServerError)
} else {
err := json.NewEncoder(w).Encode(insurancePolicy)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
}
}
}
It uses this middleware to preload the request:
func (m Env) InsurancePolicyCtx(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var insurancePolicy InsurancePolicy
result := m.DB.First(&insurancePolicy, chi.URLParam(r, "id"))
if result.Error != nil {
w.WriteHeader(http.StatusNotFound)
return
}
ctx := context.WithValue(r.Context(), "insurancePolicy", insurancePolicy)
next.ServeHTTP(w, r.WithContext(ctx))
})
}

How to wrap API Endpoint with middleware

AM trying generate and appy the client credential accesstoken to wrap simple API endpoint. I got code that work well when all codes are in the main function. However, i woild like to have the API endpoint (/protected) outside the main function in a different directory together with the middleware.
How do I do this, so that middleware apply to every request to the server?
my main function in the parent directory
func main() {
log.Printf("Server started")
router := sw.NewRouter()
manager := manage.NewDefaultManager()
manager.SetAuthorizeCodeTokenCfg(manage.DefaultAuthorizeCodeTokenCfg)
manager.MustTokenStorage(store.NewMemoryTokenStore())
clientStore := store.NewClientStore()
manager.MapClientStorage(clientStore)
srv := server.NewDefaultServer(manager)
srv.SetAllowGetAccessRequest(true)
srv.SetClientInfoHandler(server.ClientFormHandler)
manager.SetRefreshTokenCfg(manage.DefaultRefreshTokenCfg)
srv.SetInternalErrorHandler(func(err error) (re *errors.Response) {
log.Println("Internal Error:", err.Error())
return
})
srv.SetResponseErrorHandler(func(re *errors.Response) {
log.Println("Response Error:", re.Error.Error())
})
router.HandleFunc("/oauth2/token", func(w http.ResponseWriter, r *http.Request) {
srv.HandleTokenRequest(w, r)
})
router.HandleFunc("/credentials", func(w http.ResponseWriter, r *http.Request) {
clientId := uuid.New().String()[:8]
clientSecret := uuid.New().String()[:8]
err := clientStore.Set(clientId, &models.Client{
ID: clientId,
Secret: clientSecret,
Domain: "http://localhost:9094",
})
if err != nil {
fmt.Println(err.Error())
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]string{"CLIENT_ID": clientId, "CLIENT_SECRET": clientSecret})
})
log.Fatal(http.ListenAndServe(":8000", router))
}
middleware API endpoint and router in a sub directory
func protecteduri(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello, I'm protected"))
}
func validateToken(f http.HandlerFunc, srv *server.Server) http.HandlerFunc {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
_, err := srv.ValidationBearerToken(r)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
f.ServeHTTP(w, r)
})
}
router
type Route struct {
Name string
Method string
Pattern string
HandlerFunc http.HandlerFunc
}
type Routes []Route
func NewRouter() *mux.Router {
router := mux.NewRouter().StrictSlash(true)
for _, route := range routes {
var handler http.Handler
handler = route.HandlerFunc
handler = Logger(handler, route.Name)
router.
Methods(route.Method).
Path(route.Pattern).
Name(route.Name).
Handler(handler)
}
return router
}
func Index(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello World!")
}
var routes = Routes{
{
"Index",
"GET",
"/",
Index,
},
{
"protecteduri",
strings.ToUpper("Get"),
"/protected",
protecteduri,
},
{
"AccessTokenRequest",
strings.ToUpper("Post"),
"/oauth2/token",
AccessTokenRequest,
},
}
Normally i add middleware in the router file as
hadler = middleware(handler)
and it apply to all the incoming request but in this case it does not
work.
The full working code is this
func main() {
manager := manage.NewDefaultManager()
manager.SetAuthorizeCodeTokenCfg(manage.DefaultAuthorizeCodeTokenCfg)
// token memory store
manager.MustTokenStorage(store.NewMemoryTokenStore())
// client memory store
clientStore := store.NewClientStore()
manager.MapClientStorage(clientStore)
srv := server.NewDefaultServer(manager)
srv.SetAllowGetAccessRequest(true)
srv.SetClientInfoHandler(server.ClientFormHandler)
manager.SetRefreshTokenCfg(manage.DefaultRefreshTokenCfg)
srv.SetInternalErrorHandler(func(err error) (re *errors.Response) {
log.Println("Internal Error:", err.Error())
return
})
srv.SetResponseErrorHandler(func(re *errors.Response) {
log.Println("Response Error:", re.Error.Error())
})
http.HandleFunc("/oauth2/token", func(w http.ResponseWriter, r *http.Request) {
srv.HandleTokenRequest(w, r)
})
http.HandleFunc("/credentials", func(w http.ResponseWriter, r *http.Request) {
clientId := uuid.New().String()[:8]
clientSecret := uuid.New().String()[:8]
err := clientStore.Set(clientId, &models.Client{
ID: clientId,
Secret: clientSecret,
Domain: "http://localhost:9094",
})
if err != nil {
fmt.Println(err.Error())
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]string{"CLIENT_ID": clientId, "CLIENT_SECRET": clientSecret})
})
http.HandleFunc("/protected", validateToken(func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello, I'm protected"))
}, srv))
log.Fatal(http.ListenAndServe(":9096", nil))
}
//main function end here
//middleware function below
func validateToken(f http.HandlerFunc, srv *server.Server) http.HandlerFunc {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
_, err := srv.ValidationBearerToken(r)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
f.ServeHTTP(w, r)
})
}
As you can see the accesstoken to request this handle function http.HandleFunc("/protected"... below is validated using the middleware function "validateToken".
http.HandleFunc("/protected", validateToken(func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello, I'm protected"))
}, srv))
What i want to do is to rewrite the main function by moving the route handle function (http.HandleFunc("/protected"...) outside the main function and still the the accesstoken get validated when i send request to the (http.HandleFunc("/protected", validateToken(...) function using the routes defined above. In the ealier code, it works but the token to the handlefunction
func protecteduri(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello, I'm protected"))
}
is not validated.
i will be adding more handle function and do not want to put all of them in the main function.
http.HandleFunc("/protected", validateToken(func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello, I'm protected"))
}, srv))

Cannot get gorilla session.Value by key

The problem is, cannot get value from session.Value in another file in the same package
In the controllers.go file
func (c *AuthenticationControllers) AdminLogin() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
//AUTHENTICATION
if answer {
w.Write([]byte("Successful Authenticated!"))
//1* Create Session
session, _ := store.Get(r, "session") //SESSION START
///Here I've setted true
session.Values["authenticated"] = true//Successful Authenticated
sessionToken, _ := uuid.NewV4() //Generate session token
session.Values["userid"] = sessionToken.String()
session.Save(r, w)
//Redirect
http.Redirect(w, r, "/admin/application", 302)
} else {
//http.Redirect(w, r, "/login", 302)
http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
}
}
}
IN middleware.go file in the same package
func (m *AuthenticationMiddleWares) RequiresLogin(handler http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
session, _ := store.Get(r, "session") //!!!
if auth, ok := session.Values["authenticated"].(bool); !ok || !auth {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
handler(w, r)
}
}
session.Values["authenticated"] //has nil value, but has to be true after authentification in order to get you access to the route
router.HandleFunc("/admin/applications", authMiddle.RequiresLogin(authContrl.Application()))
What I have done wrong? Why session.Values["authenticated"] returns "nil"
session, _ := store.Get(r, "session") //SESSION START, your session depends on the request r, which is a pointer that is created by the package that calls your controller. If you are calling session from another file, you need to ensure, that it is the same request that is passed into store.Get(). That could be the reason.

golang session variables not working. what am i doing incorrectly?

I am using the gorilla/sessions package to implement sessions. the relevant code (or at least what I think is the only relevant part) is as follows:
// Function handler for executing HTML code
func lobbyHandler(w http.ResponseWriter, req *http.Request) {
if isLoggedIn := validateSession(w, req); isLoggedIn {
lobbyTempl.Execute(w, req.Host)
} else {
homeTempl.Execute(w, map[string]string{
"loginErrors": "Must log in first",
})
}
}
// Serves the files as needed, whenever they are requested
// used for all images, js, css, and other static files
func sourceHandler(w http.ResponseWriter, r *http.Request) {
http.ServeFile(w, r, r.URL.Path[1:])
}
func loginHandler(w http.ResponseWriter, r *http.Request) {
un, pw := r.FormValue("lUn"), r.FormValue("lPw")
if usr := findUser(un, pw); usr != nil {
if createSession(w, r) {
http.Redirect(w, req, "/lobby.html", http.StatusFound)
}
} else {
homeTempl.Execute(w, map[string]string{
"loginErrors": "User not found",
})
}
}
func createSession(w http.ResponseWriter, r *http.Request) bool {
session, _ := store.Get(r, sessionName)
session.Values["isAuthorized"] = true
if err := session.Save(r, w); err != nil {
fmt.Println("saving error: ", err.Error())
return false
}
return true
}
func validateSession(w http.ResponseWriter, r *http.Request) bool {
if session, err := store.Get(r, sessionName); err == nil {
if v, ok := session.Values["isAuthorized"]; ok && v == true {
fmt.Println("Authorized user identified!")
return true
} else {
fmt.Println("Unauthorized user detected!")
return false
}
}
return false
}
func main() {
//...
// serving files for the game
http.HandleFunc("/", homeHandler)
http.Handle("/ws", websocket.Handler(wsLobbyHandler))
http.HandleFunc("/lobby.html", lobbyHandler)
http.HandleFunc("/formlogin", loginHandler)
//...
//http.HandleFunc("/*.html", SourceHandler)
if err := http.ListenAndServeTLS(*addr, "cert.pem", "key.pem", nil); err != nil {
log.Fatal("ListenAndServe:", err)
}
}`
in my html i have:
<form id="login_form" action="/formlogin" method="post">
When logging in, the request is handled within loginHandler
The user is identified correctly from the database and a session is created (via createSession()) and placed into the cookie store.
But after the redirect to lobby.html, back in loginHandler
http.Redirect(w, req, "/lobby.html", http.StatusFound)
the validation within lobbyHandler does not work. Does this have to do with the store.Save(...) altering the headers?
I'm very new to go, as well as web apps in general, so I would really appreciate feedback.
Thanks to the comments, i was able to stumble across a similar search that works for me.
session.Options = &sessions.Options{
Path: "/lobby.html",
}
I needed to make sure the cookies know where they are going to be redirected properly.

Resources