So I was writing a backend using Go, and while I was testing the login/logout function I noticed that if I try to access a page with an expired cookie I'm allowed to do so. This happens only if I use a faulty client, because if I use my browser it works correctly, which I guess means that the cookie is deleted from the web-browser but not from my back-end.
I've tested different solutions, but nothing seems to work, what I've noticed is this: I've taken note of the pointer of the map which maps the cookies and I've noticed that despite setting "authenticated" value to false when the faulty client tries to access the page the map has its value set to true.
this is the middle ware that handles pages in which the user should be logged in and this should block the faulty client from logging in because "authenticated" value from session-handler should be false ( but it is true, despite being set to false before )
func (s *server) loggedInOnly(handlerFunc http.HandlerFunc) http.HandlerFunc {
return func(writer http.ResponseWriter, request *http.Request) {
session, _ := s.store.Get(request, "session-handler")
log.Printf("loggedInOnly: %p %v", &session.Values, session.Values)
if auth, ok := session.Values["authenticated"].(bool); !ok || !auth {
http.Error(writer, "Forbidden", http.StatusForbidden)
return
}
handlerFunc(writer, request)
}
}
this is the logout function which should delete the cookie ( but it doesn't delete it internally )
func (s *server) handleLogout() http.HandlerFunc {
return func(writer http.ResponseWriter, request *http.Request) {
session, _ := s.store.Get(request, "session-handler")
session.Values["authenticated"] = false
session.Options.MaxAge = -1
session.Save(request, writer)
log.Printf("logout: %p, %v", &session.Values, session.Values)
fmt.Fprint(writer, "Succesfully logged out")
log.Printf("%v was logged out", writer)
}
}
and this is the login function
func (s *server) handleLogin() http.HandlerFunc {
return func(writer http.ResponseWriter, request *http.Request) {
log.Printf("handle login called")
session, _ := s.store.Get(request, "session-handler")
// can login or not
session.Values["authenticated"] = true
session.Save(request, writer)
Success(writer, "successfully logged in")
}
}
I should expect the cookie to be invalid from my webserver so that when the faulty client tries to reconnect authenticated is set to false and he shouldn't be able to access loggedInOnly pages, instead I he can because authenticated value is set to true ( despite being set to false right before ).
these are the logs:
2019/01/05 17:00:00 loggedInOnly: 0xc0421960b0 map[authenticated:true]
2019/01/05 17:00:00 logout: 0xc0421960b0, map[authenticated:false]
2019/01/05 17:00:01 loggedInOnly: 0xc0420a4560 map[authenticated:true]
2019/01/05 17:00:01 logout: 0xc0420a4560, map[authenticated:false]
as you can see "authenticated" is set before to false then immediatly after it is true despite nothing happening in between
Related
I have the following middleware, first sets currentUser in gorilla/context to current user acquired from database, and the second checks if currentUser exists and redirects otherwise:
package main
import (
"database/sql"
"github.com/gorilla/context"
"log"
"net/http"
"server/helpers"
)
func withCurrentUser(db *sql.DB, next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
userId := helpers.GetCurrentUserId(db, r)
if userId == nil {
next.ServeHTTP(w, r)
return
}
row := db.QueryRow("SELECT username FROM User WHERE id=?", userId)
var username string
switch err := row.Scan(&username); err {
case sql.ErrNoRows:
next.ServeHTTP(w, r)
return
case nil:
user := helpers.User{UserId: *userId, LoggedIn: true, Username: username}
context.Set(r, "currentUser", user)
default:
log.Fatal(err)
}
next.ServeHTTP(w, r)
})
}
func loginRequired(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
user, ok := context.Get(r, "currentUser").(helpers.User)
log.Println(user, ok)
if !ok {
http.Redirect(w, r, "/login", 301)
return
}
next.ServeHTTP(w, r)
})
}
Then when I register a route that requires authenticated user, I do the following:
router.Handle("/create_post",
withCurrentUser(db, loginRequired(http.HandlerFunc(createPostGet))),
).Methods(http.MethodGet)
Where createPostGet:
func createPostGet(w http.ResponseWriter, r *http.Request) {
tmpl := template.Must(template.ParseFiles("templates/createPost.html"))
user := context.Get(r, "currentUser").(helpers.User)
_ = tmpl.Execute(w, helpers.FormStruct{CurrentUser: user})
}
My problem is: even if user is authenticated and context is populated correctly, this route always redirects to login. I tried setting breakpoints inside loginRequired and adding log.Println (as you can see in code), and that function doesn't seem to be even called (no breakpoints stop, no log output, too).
What's happening and how to make sure loginRequired is called every time and checks context properly?
UPD: It doesn't seem to persist, I recompiled the app a few times and now it's working. Anyway, what might be the reason for such behavior (I'm positive I saved everything the first time).
UPD 2: I found out that the problem is connected to browser caching, but I still have no idea why it happens. When I disable browser caching, everything works and function is called every time, but while browser cache is enabled, the function isn't called at all. Any ideas?
OMG, that was stupid. I was using 301 redirect code in loginRequired, so redirect was permanent and browser didn't even make requests.
I am developing a RESTful API with Go but owing to app configuration, authentication etc I have quite a few global variables.
I am using Julien Schmidt's httprouter because of popular recommendation and am looking for a feasible way to avoid global variables.
Here is some of the code.
I am using a middleware for authenticating a user using gorrila/securecookie.
func AuthMiddleware(handler httprouter.Handle, isLoggedIn bool) httprouter.Handle {
return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {if isLoggedIn {
currUser, err := GetCurrentUser(r)
if currUser.Username != "" {
handler(w, r, ps)
return
}
responseWriter(w, false, "User not logged in", nil)
return
}
handler(w, r, ps)
}
}
After this, I want to be able to use the currUser object inside the handler that the request is forwarded to such as this one instead of calling GetCurrentUser once again
func foobar(w http.ResponseWriter, _ *http.Request, _ httprouter.Params) {
var currQuestion CurrQuestions
err = Db.db.Raw("SELECT * FROM users WHERE U.user_id = ?", currUser.UserID).Find(&currQuestion).Error
if err != nil {
responseWriter(w, false, "Internal Server Error", "")
return
}
responseWriter(w, true, "", GameData{})
}
You can use the context package. Values stored in context are only accessible in the current controller. In your middleware (at the end) do the following:
ctx := context.WithValue(r.Context(), "User", user)
r = r.WithContext(ctx)
handler(w, r, ps)
Now you can fetch this value in you handler:
user, ok := r.Context().Value("User").(<type of your user struct>)
The string "User" is the key that is used to save the value. You can change it any way you like, but a const is a good idea.
if ok is true, the user has been fetched and is the correct type. If ok is false, the user was not set or it has the wrong type.
I've got a pretty solid grip on how to pass data from a handler to the handler it wraps, but is there a go idiomatic way to get something back from the wrapped handler? Here's a motivating example: I have an accessLogHandler and an authHandler. accessLogHandler logs every http request, with timings and other request info such as the currently logged in user's ID (if there is one). authHandler is for routes that need a logged in user, it 403's when a user isn't logged in. I want to wrap some (but perhaps not all) of my routes with the authHandler, and wrap all of my routes with the accessLogHandler. If a user is logged in, I would like my accessLogHandler to log the user info along with the access log.
Now, I have a solution I've come up with that I don't like. I'll add the code and then explain some of my issues with it.
// Log the timings of each request optionally including user data
// if there is a logged in user
func accessLogHandler(fn http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
accessLog := newAccessLog()
ctx := context.WithValue(r.Context(), accessLogKey, accessLog)
fn.ServeHTTP(w, r.WithContext(ctx))
// Logs the http access, ommit user info if not set
accessLog.Log()
}
}
// pull some junk off the request/cookies/whatever and check if somebody is logged in
func authHandler(fn http.HandlerFunc) http.HandlerFunc {
return func (w http.ResponseWriter, r *http.Request) {
//Do some authorization
user, err := auth(r)
if err != nil{
//No userId, don't set anything on the accesslogger
w.WriteHeader(http.StatusForbiddend)
return
}
//Success a user is logged in, let's make sure the access logger knows
acessLog := r.Context().Value(accessLogKey).(*AccessLog)
accessLog.Set("userID", user.ID)
fn.ServeHTTP(w, r)
}
}
Basically, what I'm doing here is attaching an accessLog struct to my context inside the accessLogHandler and inside the authHandler I'm reading accessLog from the context and calling accessLog.Set to inform the logger that a userID is present.
Some things I don't like about this approach:
context is immutable, but I'm sticking a mutable struct on it and mutating said struct elsewhere downstream. Feels like a hack.
My authHandler now has a package level dependency on the accessLog package, since I'm type asserting to *AccessLog.
Ideally my authHandler would have some way of informing any part of the request stack about user data without tightly coupling itself to said parts.
Context itself is an interface, so you could create a new logger context in the logger middleware that has the methods you would need to get the behavior you are after.
Something like this:
type Logger struct{}
func (l *Logger) SetLogField(key string, value interface{}) {// set log field }
func (l *Logger) Log(){// log request}
type LoggerCtx struct {
context.Context
*Logger
}
func newAccessLog() *Logger {
return &Logger{}
}
func accessLogHandler(fn http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// create new logger context
ctx := &LoggerCtx{}
ctx.Context = r.Context()
ctx.Logger = newAccessLog()
fn.ServeHTTP(w, r.WithContext(ctx))
// Logs the http access, ommit user info if not set
ctx.Log()
}
}
// pull some junk off the request/cookies/whatever and check if somebody is logged in
func authHandler(fn http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
//Do some authorization
user, err := auth(r)
if err != nil {
//No userId, don't set anything on the accesslogger
w.WriteHeader(http.StatusForbiddend)
return
}
//Success a user is logged in, let's make sure the access logger knows
ctx := r.Context()
// this could be moved - here for clarity
type setLog interface {
SetLogField(string, interface{})
}
if lctx, ok := ctx.(setLog); ok {
lctx.SetLogField("userID", user.ID)
}
fn.ServeHTTP(w, r.WithContext(ctx))
}
}
I setup a middleware and router to handle an AJAX request on path /api/on like this
http.HandleFunc("/",route.IndexHandler)
http.HandleFunc("/login",route.GoogleLoginHandler)
http.HandleFunc("/auth/google",route.GoogleCallbackHandler)
http.Handle("/dashboard",negroni.New(
negroni.HandlerFunc(route.IsAuthenticated),
negroni.Wrap(http.HandlerFunc(route.DashboardHandler)),
))
http.Handle("/api/on",negroni.New(
negroni.HandlerFunc(route.IsAuthenticated),
negroni.Wrap(http.HandlerFunc(route.TurnOn)),
))
Middleware IsAuthenticated allows to jump to the next router if there is a user session in my web, otherwise it will redirect
func IsAuthenticated(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
session, _ := util.GlobalSessions.SessionStart(w, r)
defer session.SessionRelease(w)
if session.Get("profile") == nil {
http.Redirect(w, r, "/", http.StatusMovedPermanently)
} else {
next(w, r)
}
}
And this is TurnOn handler for later process
func TurnOn(w http.ResponseWriter, r *http.Request) {
session, _ := util.GlobalSessions.SessionStart(w, r)
defer session.SessionRelease(w)
w.Header().Set("Access-Control-Allow-Origin","*")
w.Header().Set("Access-Control-Allow-Methods","POST")
w.Header().Set("Access-Control-Allow-Headers","Content-Type")
fmt.Println(r.URL)
}
After I turned on server, I conducted a POST request with JavaScript's client-side code using AJAX, but the console logged nothing. It seems that the TurnOn function was not reached even when AJAX request was sent successfully. Did I miss something obviously?
I've built an application that uses the Go Gorilla sessions package. Everything seems fine, except when on logout I implement
func logout(w http.ResponseWriter, r *http.Request) {
session, _ := store.Get(r, "authsesh")
session.Values["access"] = "denied"
session.Save(r, w)
http.Redirect(w, r, "/", 302)
return
}
Because the page requiring authentication is cached by the browser, it can still be accessed after logout. How can I get around that? Is there a way to prevent the browser from caching the page? There's nothing wrong with the cookie, if I clear the cache and keep the cookie I can see the logout has had the desired effect.
Set the correct cache headers in your handler(s):
w.Header().Set("Cache-Control", "no-cache, private, max-age=0")
w.Header().Set("Expires", time.Unix(0, 0).Format(http.TimeFormat))
w.Header().Set("Pragma", "no-cache")
w.Header().Set("X-Accel-Expires", "0")
Note that we set multiple headers to account for proxies and HTTP/1.0 clients.
You can wrap these into middleware you can apply as well:
func NoCache(h http.Handler) http.Handler) {
fn := func(w http.ResponseWriter, r *http.Request) {
// Set the headers
}
return http.HandlerFunc(fn)
}
// In your router
http.Handle("/user-dashboard", NoCache(http.HandlerFunc(YourDashboardHandler))