Sessions are a popular option for user authentication across the web. A session is a period during which a user actively engages with an application. The lifetime of a session begins when a user logs in and ends when they log out.

HTTP is a stateless protocol, so you’ll often need to track user activity manually.

On the server side of your application, you can generate a unique value, preferably a cryptographically secure one. You can then store that in a cookie which the client will send to your app on future requests, creating a form of state.

Sessions in Go

You can use the net/http package to implement sessions, and there are many available packages that already do this. The most popular is the Gorilla sessions package. This package provides cookie and file storage functionality alongside custom session backend infrastructure.

Run this command on your Go workspace to install the Gorilla sessions package.

        go get github.com/gorilla/sessions

In this tutorial, you’ll use a cookie store for sessions. You’ll use the net/http package to start a web server that will check the user’s issue and revoke sessions.

Here’s the list of imports you’ll need to follow this tutorial.

        import (
    "github.com/gorilla/sessions"
    "log"
    "net/http"
)

The log package is for logging-related operations based on the status of the user’s authentication.

You’ll need a cookie store for your login and logout handler functions. For your cookie store, you’ll need a secret key for authentication.

Here’s a function for the cookie store implementation.

        // cookieStore function creates a cookie store with the user's secret key
func cookieStore() *sessions.CookieStore {
    SecretKey := []byte("super-secret-SecretKey")
    cookieStore := sessions.NewCookieStore(SecretKey)
 
    // function returns the cookie store so other functions can access it
    return cookieStore
}

In the cookieStore function, the declared secret key variable SecretKey is an example secret key. In production, your secret key should be cryptographically secure, using the crypto package for example. You should also load the secret from an environment variables file.

The function returns a value of the *sessions.CookieStore type which represents the cookie store secured with the secret key. You’ll use the CookieStore function in your login and logout handlers to authenticate users and assign sessions.

The Login Handler Function

You’ll want to verify if the user is already logged in before creating a session in your login handler function. You can use the Get method on the cookie store to retrieve a session from the cookie and add the session to the client’s request.

The Gorilla session Get method implementation

The Get method returns the session and an error you can handle. If you need to authenticate the user, you can authenticate or authorize in the login handler.

        // login handler retrieves the session from the cookie Store
func login(writer http.ResponseWriter, request *http.Request) {
    session, err := cookieStore().Get(request, "Cookie Name From Request")
 
    if err != nil {
        log.Fatalln(err)
    }
 
    // set your user authentication here based on the operation
    session.Values["auth status"] = true
    err = session.Save(request, writer)
 
    if err != nil {
        return
    }
}

The Values property holds the session-related data in the cookie store:

Gorilla session Values method implementation

The Save method saves the session to the cookie store. In your handlers, you’ll need other authentication measures for higher security.

Verifying a User’s Login Status

Your verification handler should retrieve the session from the client’s cookie using the cookie store’s Get method. Then you can retrieve the session and authenticate the user.

        func checkAuthStatus(writer http.ResponseWriter, request *http.Request) {
    session, err := cookieStore().Get(request, "Cookie Name From Request")
 
    if err != nil {
        log.Fatalln(err)
    }
 
    authenticated := session.Values["auth status"]
 
    if authenticated == true {
        writer.WriteHeader(http.StatusOK) // write 200 status code
        return
    } else {
        writer.WriteHeader(http.StatusBadRequest) // write 400 http status code
        return
    }
}

The authenticated variable uses the Values property to retrieve the status from the cookie store. The if statement then verifies this authentication status. If it evaluates to true, the client receives the 200 HTTP status code. If the authentication status isn’t true, the client receives the 400 HTTP status code.

The Session Logout Handler

Your logout handler function will be very similar to the login handler function. You will delete all data related to the user’s session from the cookie store and nullify the authentication status.

        func logout(writer http.ResponseWriter, request *http.Request) {
    session, err := cookieStore().Get(request, "Cookie Name From Request")
 
    if err != nil {
        return
    }
     
    // nullify the user's session from the cookie Store
    session.Values["auth status"] = false
    err = session.Save(request, writer)
 
    if err != nil {
        return
    }
}

The logout handler function nullifies the user’s session authentication status and saves the status to the cookie store.

Don’t Store Sensitive Data in Sessions

Sessions are great for storing data, but it’s best to avoid storing sensitive data in them. An attacker can hijack a session if you store its data in a cookie and send it over plain HTTP. The security of your app is important to your users.

Sessions are stateful and there are many database implementations of cookie stores for the Gorilla package, for both SQL and NoSQL databases.