Introduction
The Authentication Session of a web app is the heart of it's defense against malicious threats. Hence, it is among the first point of recon for a security tester. In this article, the Go developer will be enlightened on the authentication sessions of a web app, vulnerabilities and design flaws in authentication sessions, the difference between Session-Based and Token-Based Authentication methods, and when to apply each. Example use cases for both methods are also given.
Authentication Sessions
A web application’s Authentication protocol is in sessions. The procedure follows like this:
- A client sends an authentication request to the log in session of the web app
- The web app receives it and sends to the web server
- The web server matches it with existing credentials
- The web server returns a cookie if a match is found, and then the relevant REST API is called to the needed page.
When web developers build authentication mechanisms, they often rely on either of these methods:
- Using HTML forms
- Using Multifactor mechanisms
- Client SSL-Certificates
- HTTP authentication
- Authentication Services
- JSON Web Tokens
Let's take a look at each of these methods and what they imply.
Using HTML Forms
HTML forms are the most common methods of authenticating web applications, where username and password are collected and submitted to the application. This mechanism accounts for well over 90% of applications available on the internet.
Using Multifactor Mechanisms
In platforms where more security is required, like in online payment systems, a multistage form filling session is initiated, and users are mandated to provide additional credentials. In banking apps, physical tokens are often required. These tokens typically produce a stream of one-time passcodes (OTPs) or provide challenges that require input from the user. The rule of thumb is to make use of OTPs for highly sensitive data.
Client SSL-Certificates
Some web apps make use of SSL Certificates or cryptographic mechanism. The process involves:
- Purchasing and/or Generating a Client Authentication Certificate.
- Completing the Validation Process.
- Downloading or Exporting the User's Client Certificate.
- Importing the Client Authentication Certificate to OS & Browser Certificate Stores.
- Configuring the Server to Support Client Authentication.
- Testing the Certificate to Ensure It Works.
HTTP Authentication
HTTP based authentication is extremely rare in internet adoption. It is used more on intranet basis. It the most basic form of authentication. In HTTP authentication, client login credentials are sent in the request headers with each request like:
Authorization: Basic YWxqY2U6cGE2NXdbcmQ=
Basic authentication doesn't use encryption on username and password. As such, it's application is limited to cases where there is low value data, and need for easy access. Even at that, it is advisable to use it: -On HTTPS connections -With really strong passwords -With rate limiting added to prevent brute forcing attacks
Authentication Services
Authentication services are gaining weight in terms of application and credibility. A popular and widely used authentication and authorization service is the Auth0. Auth0 is an easy to implement, adaptable authentication and authorization platform that has gain wide popularity and use among developers.
JSON Web Tokens
JWT, or JSON Web Token, is an open standard authentication protocol used to share security information between two parties — a client and a server. The client credentials are not stored to the server in this case, but transferred back to the client for safekeeping and reuse.
Vulnerabilities in Authentication Sessions
The vulnerabilities and attacks that are possible in HTML forms will most likely work in other methods of authentication, albeit with a little upgrade. Let's take a look at design flaws that can be exploited in an attack.
Design Flaws in Web Applications
1. Weak Passwords
Weak Passwords are a thing in this era, still. They take the form of short passwords, predictable words, user’s name that can be easily gotten through social engineering, words or names with obvious symbols representing vowels, etc.
Exploitation:
The first thing hackers do is to attempt logging into the web application and taking note of the rules specified by the software in filling password boxes.
2. Password Change Functionality
Web developers often fail to provide password change Functionality in their apps. This feature is very important in web apps, though, for two especial reasons:
- For the user to quickly change their password when they detect malicious activity
- For periodic testing, and validation of a password change session
The flaws in web apps that don't use a Password Change Functionality include:
- Verbose error messages
- Allowing unrestricted guessing in the existing password field
- Matching “new password” and “confirm new password” fields only after validating the existing password
Other design flaws in authentication sessions are present in the Forgotten Password Functionality, Remember Me Functionality, and Invalid Credentials Functionality Let's learn how to build authentication sessions in Go.
Basic HTTP Authentication in Golang
As explained earlier, the basic HTTP authentication method is not the safest. However, it can be implemented with hashing in the following way:
func basicAuth(next http.HandlerFunc) http.HandlerFunc {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
username, password, ok := r.BasicAuth()
if ok {
usernameHash := sha256.Sum256([]byte(username))
passwordHash := sha256.Sum256([]byte(password))
expectedUsernameHash := sha256.Sum256([]byte("username"))
expectedPasswordHash := sha256.Sum256([]byte("password"))
usernameMatch := (subtle.ConstantTimeCompare(usernameHash[:], expectedUsernameHash[:]) == 1)
passwordMatch := (subtle.ConstantTimeCompare(passwordHash[:], expectedPasswordHash[:]) == 1)
if usernameMatch && passwordMatch {
next.ServeHTTP(w, r)
return
}
}
w.Header().Set("WWW-Authenticate", `Basic realm="restricted", charset="UTF-8"`)
http.Error(w, "Unauthorized", http.StatusUnauthorized)
})
}
It is important to emphasize that the username and password are not being hashed for the purpose of storage. They are hashed to get two equal-length byte slices that can be compared in constant-time. In using this method of HTTP Basic Authentication, the imported packages include:
import (
"crypto/sha256"
"crypto/subtle"
"fmt"
"log"
"net/http"
"os"
"time"
)
An application instance of struct type should be created to contain the username and password:
type application struct {
username string
password string
}
The main function will contain the entire operations and the server instance:
func main() {
webapp := new(application)
webapp.auth.username = os.Getenv("AUTH_USERNAME")
webapp.auth.password = os.Getenv("AUTH_PASSWORD")
if webapp.auth.username == "" {
log.Fatal("Illegal username provided")
}
if webapp.auth.password == "" {
log.Fatal("Illegal password provided")
}
mux := http.NewServeMux()
mux.HandleFunc("/unprotected", webapp.unprotectedHandler)
mux.HandleFunc("/protected", webapp.basicAuth(app.protectedHandler))
srv := &http.Server{
Addr: ":8080",
Handler: mux,
IdleTimeout: time.Minute,
ReadTimeout: 10 * time.Second,
WriteTimeout: 30 * time.Second,
}
log.Printf("starting server on %s", srv.Addr)
err := srv.ListenAndServeTLS("./localhost.pem", "./localhost-key.pem")
log.Fatal(err)
}
Session-Based Authentication Session in Golang
Session-Based authentication systems are often used in web apps that implement server side templating. OAuth and OpenID could be added to further secure the application.
In Golang, the popularGorilla Mux
package has a gorilla/sessions
package that can be used to create authentication sessions.
Hence, the first step in creating an authentication session is to install the gorilla/mux
and gorilla/sessions
packages.
go get github.com/gorilla/mux
go get github.com/gorilla/sessions
After this, create a local directory for the project.
Next, import the gorilla/mux
package as well as other important packages.
package main
import (
"log"
"net/http"
"os"
"time"
"github.com/gorilla/mux"
"github.com/gorilla/sessions"
)
Seeing that we are creating session-based authentication for API endpoints, we will create a cookie store using the sessions.NewCookieStore
method in the sessions package we imported.
var store =sessions.NewCookieStore([]byte(os.Getenv("SESSION_SECRET")))
Assuming we are logging into a dashboard, then the necessary API endpoints are:
/login
/dashboard
/logout
There will be a list of users having their own dashboards, so the server must have a map it can search for yours in. Initializing an example user credentials:
var users = map[string]string{
"Mac": "username",
"admin": "password"
}
A login handler function would be responsible for the client's request, credential matching, and return of the /dashboard endpoint. The function will initially parse the POST form. Then, it will retrieve the client's credentials.
func LoginHandler(w http.ResponseWriter, r *http.Request) {
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")
}
The function is also going to match the collected data with the ones stored in the server. The function will authenticate an existing match, and return error when there's no match.
//continued in the LoginHandler function
if originalPassword, ok := users[username]; ok {
session, _ := store.Get(r, "session.id")
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"))
The login either succeeds or not, depending on the availability of the client on the server’s credential store.
The log out session, on the other hand, receives a GET request and turns the session.Values
method to false.
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(""))
}
The session.Save
method saves the cookie state after modification.
Next, it is important to create the /dashboard
API endpoint. The /dashboard
function would return the time of login:
func DashboardHandler(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)
}
}
After we finished writing the endpoints, the next step is to write the main
function. This function will connect and start up all endpoints, and the server at an open port:
main() {
server := mux.NewRouter().StrictSlash(True)
server.HandleFunc("/login", LoginHandler)
server.HandleFunc("/dashboard", DashboardHandler)
server.HandleFunc("/logout", LogoutHandler)
http.Handle("/", server)
srv := &http.Server{
Handler: server,
Addr: "127.0.0.1:8080",
// Good practice: enforce timeouts for servers you create!
WriteTimeout: 15 * time.Second,
ReadTimeout: 15 * time.Second,
}
log.Fatal(srv.ListenAndServe())
}
This completes the secure authentication session. Running the command go run main.go
starts the server at localhost:8080
.
Token-Based Authentication Session in Golang
One of the downsides of this token- or session-based authentication system is that it stores credentials to the program memory or on a special server software like Redis. However, JWT poses a solution to this. What JWT does is to resend the login credentials to the client to store in a database. The entire procedure is explained below:
- The client sends login credentials as a POST request to the login API endpoint
- The server authenticates the details and, if successful, it generates a JWT
- The server returns this to instead of creating a cookie. It becomes the client's responsibility to store this token
- Since the client is in charge of the token, it needs to add this in the headers section to make subsequent REST API calls
- The server checks the JWT from the header and if it is successfully decoded, the server authenticates the client
For RESTful APIs, token-based authentication is the best and recommended approach given that it is stateless.
In creating a JWT Token-Based session with go, the
jwt-go
package will be imported. It has a NewWithClaims method that accepts a signing method and a claims map. A great guide on implementing JWT with Go is available on Auth0 blog.
Conclusion
At this point, the reader should be aware of what authentication sessions are under the covers, the kind of vulnerabilities possible in authentications, popular options in authenticating web apps, and the difference and ways to write session- and token-based authentication sessions.