Authentication Protocols and Building Authentication Sessions with Golang
The Authentication Session of a web app is the heart of its defense against malicious threats. Hence, it is among the first points of recon for a security tester.
This article will discuss the authentication sessions of a web app in the “Go” programming language (Golang). It will also discuss the vulnerabilities and design flaws in authentication sessions, the difference between Session-Based and Token-Based Authentication methods, and when to apply each.
I will also share the example use cases for both methods with actual codes for building authentication sessions in Golang.
What Are Authentication Protocols?
An authentication protocol is a system of rules that defines how entities on a network prove their identity to each other to establish a secure communication channel. It involves validating the credentials provided by a user or a system to verify their identity. This process is crucial for controlling access to resources and services in a secure manner.
Here are the key components and steps typically involved in an authentication protocol:
- Credentials: These are the pieces of information used to prove an identity. Credentials often include a username and password but can also encompass tokens, digital certificates, biometric data, and one-time passcodes.
- Authentication Server: This system is responsible for verifying the credentials against a stored user information database. If the credentials are valid, the authentication server will affirm the identity of the user or entity.
- Secure Transmission: Credentials are usually transmitted over secure channels, such as HTTPS, which encrypts the data during transmission, to prevent unauthorized access or tampering.
- Response: After verifying the credentials, the authentication server sends a response to the requesting entity. This response could be a success or failure message or include a token or session key indicating the authenticated session.
- Session Management: Once authenticated, the session is maintained through mechanisms like session cookies or tokens, allowing the user to access permitted resources without needing to re-authenticate repeatedly.
Types of Authentication Protocols for Web Applications:
HTTP Basic Authentication:
This is a simple authentication scheme built into the HTTP protocol. The client sends HTTP requests with the ‘Authorization‘ header that contains the word ‘Basic‘ followed by a space and a base64-encoded string ‘username:password‘. However, it is not the most secure method unless used in conjunction with TLS (HTTPS) as the credentials are only encoded, not encrypted.
Session-Based Authentication:
In this protocol, when the user logs in, the server creates a session for the user and stores it either in the server’s memory or in a session store. It then sends a cookie containing the session ID back to the client. The client sends this cookie in subsequent requests, and the server can validate it and retrieve the session information.
Token-Based Authentication (e.g., JWT):
The server creates a JSON Web Token (JWT) and sends it to the client upon successful authentication. The client then sends this token in the HTTP headers for subsequent requests. The server verifies the token to ensure it’s valid and not tampered with. JWTs can include expiry times and other validation information.
OAuth:
Often used to allow third-party services to access user resources without exposing their password. OAuth works over HTTPS and authorizes devices, APIs, servers, and applications with access tokens rather than credentials.
OpenID Connect:
Built on top of the OAuth 2.0 protocol, it allows clients to verify the user’s identity and obtain basic profile information in an interoperable and REST-like manner. It is used by many major companies, including Google and Facebook.
SAML (Security Assertion Markup Language):
SAML is used to exchange authentication and authorization data between parties, particularly between an identity provider and a service provider. This is particularly common in enterprise federated identity scenarios.
The choice of authentication protocol depends on the specific requirements and constraints of the web application, including the need for security, scalability, user experience, and interoperability.
What is an Authentication Session?
A web application’s Authentication protocol is in its sessions.
An authentication session refers to the period during which a user’s or client’s identity is considered verified after successfully logging in or authenticating to a system or service. This session maintains the user’s state and tracking within the application or across multiple applications, allowing the user to access multiple resources without needing to re-authenticate each time.
Flow of an Authentication Session
Here’s how an authentication session typically works:
- Login: The user provides credentials (such as a username and password) to the system.
- Verification: The system verifies the credentials against stored data. If the credentials are correct, the authentication process is successful.
- Session Creation: Once authenticated, the system creates a session. This session is often represented by a session identifier (session ID) that is unique to that particular interaction.
- Session ID Storage: In web applications, the session ID is usually stored on the client side within a cookie. It can also be stored in other forms, like local storage, or sent with each request in token-based systems like those using JWT (JSON Web Tokens).
- Session Usage: With each subsequent request to the server, the session ID is sent back to the server either through cookies or headers. The server checks this session ID to retrieve the session data, confirming that it is still valid and that it corresponds to an authenticated user.
- Session Expiry/Logout: Sessions have a defined lifetime after which they expire for security reasons. A user can also manually end a session by logging out. Upon expiration or logout, the session is invalidated, and the user would need to re-authenticate to start a new session.
Authentication sessions are crucial for maintaining security. They reduce the number of times a user needs to send their credentials over the Internet, thereby minimizing exposure to potential threats. They also enhance the user experience by allowing seamless access to multiple services or resources without repeated logins.
Building an Authentication Session
To summarize, the steps to build the authentication session are as follows:
- A client sends an authentication request to the login session of the web app
- The web app receives it and sends it to the webserver
- 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 have 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
A multistage form-filling session is initiated in platforms requiring more security, like online payment systems, and users must provide additional credentials. In banking apps, we often require physical tokens. These tokens typically produce a stream of one-time passcodes (OTPs) or provide challenges requiring user input.
The rule of thumb is to use OTPs for highly sensitive data.
Client SSL-Certificates
Some web apps make use of SSL Certificates or cryptographic mechanisms.
HTTP Authentication
HTTP-based authentication is extremely rare in internet adoption. It finds its use more on an intranet. It is 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, its application is limited to cases with low-value data and the 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 & authorization service is Auth0.
JSON Web Tokens
JWT, or JSON Web Token, is an open standard authentication protocol to share security information between two parties – a client and a server. The client credentials are not stored on the server 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 authentication methods, albeit with a bit of upgrade.
Let’s look at design flaws that a hacker can exploit in an attack.
Design Flaws in Web Applications
1. Weak Passwords
Weak passwords remain a significant design flaw in many web applications, posing serious security risks. Despite advancements in security practices, the prevalence of easily guessable passwords—such as short phrases, predictable words, or personal information easily found on social media—remains high. In the context of Golang-based web applications, addressing this flaw involves:
- Enforcing Strong Password Policies: Implementing stringent password requirements, like minimum length and complexity, to prevent common weak passwords.
- User Education: Highlighting the importance of strong passwords to users and providing guidelines for creating secure passwords.
- Alternative Authentication Methods: Incorporating methods like two-factor authentication (2FA) and biometrics can enhance security beyond traditional passwords.
- Utilizing Golang’s Features: Leveraging Golang’s robust standard library and security features to build more secure authentication systems.
By focusing on these areas, web applications can significantly reduce the risks associated with weak passwords and enhance overall security.
Exploitation
First, hackers attempt to log into the web application and take 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 vital in web apps, though, for two particular 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, we can implement it 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 purpose of hashing the username and password is not for storage but rather to generate two equal-length byte slices. These slices are then compared in “constant time” to enhance security and prevent timing attacks.
What is “Constant Time”?
“Constant time” in the context of comparing hashed values, such as usernames and passwords, refers to an important concept in computer security. It means that the time it takes to compare two values does not depend on the values themselves. This property is crucial for security, especially in authentication systems. Here’s why:
- Preventing Timing Attacks: If a comparison takes different amounts of time based on how much of the input matches, an attacker could potentially use the time it takes to compare values to infer information about the stored data. For example, if a system is checking a password, and it takes longer to respond when the first few characters are correct, an attacker can use this information to guess the password one character at a time.
- Uniform Response Time: In constant-time comparison, the operation takes the same amount of time regardless of the input data. This means that whether the comparison is correct or incorrect or how much of the input data matches, it won’t affect the time it takes to complete the comparison. This uniformity helps in masking which part of the data, if any, was correct, thereby thwarting timing attacks.
In summary, performing constant-time comparisons of hashed values is a security best practice to prevent attackers from gaining insights into sensitive data based on the time it takes to perform these comparisons.
Use of the HTTP Basic Authentication method, 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 secure the application further.
In Golang, the popular Gorilla 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. The code for creating an authentication session is as follows:
go get github.com/gorilla/mux
go get github.com/gorilla/sessions
Creation of Local Directory
After this, create a local directory for the project.
Next, import the gorilla/mux package and other important packages. Please check the code below:
package main
import (
“log”
"net/http"
“os”
“time”
“github.com/gorilla/mux”
“github.com/gorilla/sessions”
)
Creation of Cookie Store
Since we are creating session-based authentication for API endpoints, we will create a cookie store using the “sessions.NewCookieStore” method in the imported sessions package. Please check the code below:
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 with their own dashboards. So, the server must have a map in which it can search for yours. Initializing an example user credentials:
var users = map[string]string{
"Mac": "username",
"admin": "password"
}
Retrieving Clinet’s Credentials
A login handler function would be responsible for the client’s request, credential matching, and the /dashboard endpoint return.
The function will initially parse the POST form. Then, it will retrieve the client’s credentials as follows:
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 will also match the collected data with the ones stored on the server. Finally, the function will authenticate an existing match and return an 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"))
Login and Logout Sessions
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.
Endpoints
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 finish 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. JWT resends 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 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 must 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
In this article, we discussed what authentication sessions are under the covers. We also discussed the kinds of vulnerabilities possible in authentications, popular options in authenticating web apps, and the differences and ways to write session- and token-based authentication sessions.
MacBobby Chibuzor is a Robotics Hardware Engineer and a Tech Polyglot. He also has practical experience in Software Engineering and Machine Learning, with an interest in embedded systems and Blockchain technology. In addition, Mac loves to spend his free time in technical writing.