Session-based authentication

Create a project called simpleAuth in GOPATH and add a main.go file, which holds the logic for our program:

mkdir simpleAuth
touch main.py

In this program, we are going to see how we can create a session-based authentication using the Gorilla sessions package. Refer to the following code snippet:

package main
import (
"log"
"net/http"
"os"
"time"
"github.com/gorilla/mux"
"github.com/gorilla/sessions"
)
var store =
sessions.NewCookieStore([]byte(os.Getenv("SESSION_SECRET")))
var users = map[string]string{"naren": "passme", "admin": "password"}
// HealthcheckHandler returns the date and time
func HealthcheckHandler(w http.ResponseWriter, r *http.Request) {
session, _ := store.Get(r, "session.id")
if (session.Values["authenticated"] != nil) && session.Values["authenticated"] != false {
w.Write([]byte(time.Now().String()))
} else {
http.Error(w, "Forbidden", http.StatusForbidden)
}
}
// LoginHandler validates the user credentials
func LoginHandler(w http.ResponseWriter, r *http.Request) {
session, _ := store.Get(r, "session.id")
err := r.ParseForm()
if err != nil {
http.Error(w, "Please pass the data as URL form encoded",
http.StatusBadRequest)
return
}
username := r.PostForm.Get("username")
password := r.PostForm.Get("password")
if originalPassword, ok := users[username]; ok {
if password == originalPassword {
session.Values["authenticated"] = true
session.Save(r, w)
} else {
http.Error(w, "Invalid Credentials", http.StatusUnauthorized)
return
}
} else {
http.Error(w, "User is not found", http.StatusNotFound)
return
}
w.Write([]byte("Logged In successfully"))
}
// LogoutHandler removes the session
func LogoutHandler(w http.ResponseWriter, r *http.Request) {
session, _ := store.Get(r, "session.id")
session.Values["authenticated"] = false
session.Save(r, w)
w.Write([]byte(""))
}
func main() {
r := mux.NewRouter()
r.HandleFunc("/login", LoginHandler)
r.HandleFunc("/healthcheck", HealthcheckHandler)
r.HandleFunc("/logout", LogoutHandler)
http.Handle("/", r)
srv := &http.Server{
Handler: r,
Addr: "127.0.0.1:8000",
// Good practice: enforce timeouts for servers you create!
WriteTimeout: 15 * time.Second,
ReadTimeout: 15 * time.Second,
}
log.Fatal(srv.ListenAndServe())
}

It is a REST API that allows one to access the health condition (up or not) of the system. In order to authenticate, one needs to call the login endpoint first. The program imported two main packages called mux and sessions from the Gorilla kit. Mux is used to link the URL endpoints of HTTP requests to a function handler, and sessions is used to create new sessions and validate existing ones on the fly. 

In Go, we need to store sessions in the program memory. We can do that by creating CookieStore. This line explicitly tells the program to create one by picking the secret key from the environment variable called SESSION_SECRET:

var store = sessions.NewCookieStore([]byte(os.Getenv("SESSION_SECRET")))

sessions has a new function called NewCookieStore that returns a store. We need to use this store to manage cookies. We can get a cookie session with this statement. If the session doesn't exist, it returns an empty one:

session, _ := store.Get(r, "session.id")

session.id is a custom name that we gave to the session. With this name, a cookie will be sent back in the client response. LoginHandler tries to parse the form that was supplied by the client as multipart form data. This step is essential in the program:

err := r.ParseForm()

This fills the r.PostForm map with the parsed key-value pairs. That API requires both username and password for its authentication. So, we are interested in scraping username and password. Once LoginHandler receives the data, it tries to check it with the details in a map called users. In a practical scenario, we use the database to validate those details. For simplicity's sake, we hardcoded values and tried to authenticate from it. If the username doesn't exist, return an error saying resource not found. If the username exists and the password is incorrect, return an UnAuthorized error message. If everything goes well, return a 200 response by setting the cookie value, like this:

session.Values["authenticated"] = true
session.Save(r, w)

The first statement sets the cookie key called "authenticated" to true. The second statement actually saves the session on the response. It takes request and response writers as the arguments. If we remove this statement, the cookie will not have any effect. Now, coming to the HealthCheckHandler, it does the same thing as LoginHandler initially, like this:

session, _ := store.Get(r, "session.id")

Then, it checks whether a given request has a cookie that has the key called "authenticated". If that key exists and is true, it means it is the user that the server authenticated previously. But, if that key does not exist or the "authenticated" value is false, then the session is not valid, hence it returns a StatusForbidden error. 

There should be a way for the client to invalidate a login session. It can do that by calling the logout API of the server. The API just sets the "authenticated" value to false. This tells the server that the client is not authenticated: 

session, _ := store.Get(r, "session.id")
session.Values["authenticated"] = false
session.Save(r, w)

In this way, a simple authentication can be implemented using the sessions in any programming language, including Go. 

Don't forget to add this statement, as it is the actual one that modifies and saves the cookie: session.Save(r, w).

Now, let us see the execution of this program. Instead of CURL, we can use a wonderful tool called Postman. The main benefit is that it runs on all platforms including Microsoft Window; no need for CURL anymore. 

The error codes can mean different things. For example, Forbidden (403) is issued when the user tries to access a resource without authentication, whereas Resource Not Found (404) is issued when the given resource does not exist on the server.
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.188.154.252