Golang lets you build web applications that are performant and easy to deploy. This tutorial will show you how to use OAuth to authenticate users in a golang application.

Securing A Golang App with OAuth

In this tutorial, we are going to learn how to secure a golang program with OAuth while using FusionAuth as the auth provider. Authentication and authorization are essential for any application, and golang apps are no different.

First, we will set up the FusionAuth server. Then, we’ll configure the golang program to construct a URL to direct a user to a login form generated by FusionAuth. Lastly, we will learn how to make use of an access token in order to get user data using OIDC.

You can follow along conceptually, or check out the GitHub repo with the complete implementation first. Now, let’s get started!

Requirements

The requirements to follow this tutorial are as follows:

  • Go SDK
  • VScode or any other text editor
  • FusionAuth
  • Homebrew (optional)

Setting up FusionAuth as your auth provider

FusionAuth lets you pick your installation method. However, in this post, we’ll install using Homebrew. The process is simple. First, we install the tap by running the following command in the terminal:

brew tap fusionauth/homebrew-fusionauth

Note that this installation step only needs to be done once. Next, we need to install the FusionAuth main service by running the following command:

brew install fusionauth-app

Lastly, we need to start the FusionAuth service by running this in the project terminal:

brew services start fusionauth-app

In order to get started, we need to open our browser and go to http://localhost:9011. Then, we need to complete the maintenance mode and the setup wizard.

Setup FusionAuth with the installation wizard

The maintenance mode form requires a super user credential for your database:

The FusionAuth maintenance mode screen, including the super user configuration.

It will use these credentials to create a database user for FusionAuth to connect as. Further down the maintenance mode page, you can specify the username and password of this user if you choose:

Adding a user for FusionAuth to connect as.

If you want to create a FusionAuth user and database using different tools, rather than maintenance mode, you can. See the silent mode documentation for more on this option.

Once you’ve created a database for FusionAuth, you’ll need to accept the license and create an initial FusionAuth account:

The FusionAuth setup wizard.

Then you’ll need to log in to this new FusionAuth account. This user has administrative privileges within the FusionAuth instance:

Logging in to the FusionAuth administrative user interface.

Create and configure a new Application

Initially, there will be only one application: FusionAuth itself. Hence, we need to create a new application by navigating to the “Applications” tab and clicking the green “+” sign.

Then, we need to configure this application. First, add the authorized callback of http://localhost:8080/callback. This is the URL which will process the authorization code should a user authenticate successfully.

In addition, make sure that the Authorization Code checkbox is checked. This is the grant we’ll be using, and the one that is recommended for most use cases.

After successful setup, view the application by clicking the green magnifying glass. The OAuth Client ID and Client Secret values can also be found on the same screen, in the “OAuth configuration” section. You’ll want to note both of those.

The application configuration screen.

We set up this application in the default tenant. FusionAuth supports multi-tenant configurations, but for this post, we’ll keep all user data in one tenant, as that makes things slightly simpler. Now, our FusionAuth setup is complete. Let’s move to the golang part.

Setting up the development environment for golang

We are going to set up a development environment for golang. You can skip this if you already have it. First, we need to download Go from its official website.

Let’s walk before we run. Here’s the simplest Go project; not surprisingly this is the typical ‘Hello World’ program. This is what it looks like:

package main
import "fmt"
func main() {
  fmt.Println("Hello, Golang")
}

Save this in a file called main.go. In the terminal, in the directory where this code is, run:

go run main.go

We see the following output:

Hello, Golang

Success! Let’s add in some authentication, shall we?

Setting up initial handlers and OAuth2 config in our golang app

We are going to set up the handlers and OAuth2 configuration. For that, we need to import necessary components such as the OAuth and HTTP packages first:

package main
import (
  "fmt"
  "io/ioutil"
  "net/http"
  "golang.org/x/oauth2"
  "github.com/thanhpk/randstr"
)
//...

Depending on your golang setup you may need to get the nonstandard packages:

go get github.com/thanhpk/randstr
go get golang.org/x/oauth2  

Then, we need to define a variable to contain the configurations as shown in the code snippet below:

//...
var (
  FusionAuthConfig *oauth2.Config
  oauthStateString = randstr.Hex(16)
)
//...

Next, we need to create a new OAuth instance and configure it in the init function. Here, we need ClientID and ClientSecret, which we got from the FusionAuth application OAuth config section. We also need to define the OAuth2 endpoints (which we can pull from the FusionAuth docs) and a callback URL (as entered in the FusionAuth config above):

//...
func init() {
  FusionAuthConfig = &oauth2.Config{
    RedirectURL: "http://localhost:8080/callback",
    ClientID:    "7d2b4cb4-ccd5-42ac-8469-f802393c8f98",
    ClientSecret:"6x9GI_FUs5TBt_ql0m1CxNy962L7SEf0Kx2KxsY7WYw",
    Scopes:      []string{"openid"},
    Endpoint: oauth2.Endpoint{
      AuthURL:  "http://localhost:9011/oauth2/authorize",
      TokenURL: "http://localhost:9011/oauth2/token",
      AuthStyle: oauth2.AuthStyleInHeader,
    },
  }
}
//...

Here, we have configured our OAuth connection inside the init function. This function is run every time the application is started. In a production application, these values should be pulled from a configuration file or a database table, rather than hard coded.

Adding our display

Now, we need to display a page. For the handleMain function, we are hard coding HTML as shown below, but for a larger application you might want to look at a templating language:

//...
func handleMain(w http.ResponseWriter, r *http.Request) {
  var htmlIndex = `<html>
    <body>
       <a href="/login">FusionAuth Log In</a>
    </body>
    </html>`
   fmt.Fprintf(w, htmlIndex)
}
//...

Then, we need to call the handleMain function inside the main function. This lets us listen to port 8080:

//...
func main() {
  http.HandleFunc("/", handleMain)
  fmt.Println(http.ListenAndServe(":8080", nil))
}
//...

Since we changed the code from the “Hello world” example, we need to restart the golang program. We need to stop the existing server. Then, restart it by using this command:

go run main.go

Navigate to http://localhost:8080/. We will get the following screen in the web browser:

The application login page.

Great, but how do we actually log in? Glad you asked.

Login function

For the login function, we need to create a new function called handleFusionAuthLogin. Then, we generate a URL and use the HTTP package to redirect the user to it. Because we are using a properly configured OAuth library, the URL is generated for us.

//...
func handleFusionAuthLogin(w http.ResponseWriter, r *http.Request) {
  url := FusionAuthConfig.AuthCodeURL(oauthStateString)
  http.Redirect(w, r, url, http.StatusTemporaryRedirect)
}
//...

Handling the callback

In order to handle the callback, which will contain the code parameter and is crucial to the Authorization Code grant, we pass the code and state variables to a getUserInfo function. We’ll see the code behind that function below, but it is going to get user data back from FusionAuth. The code to handle the callback is:

//...
func handleFusionAuthCallback(w http.ResponseWriter, r *http.Request) {
  content, err := getUserInfo(r.FormValue("state"), r.FormValue("code"))
  if err != nil {
    fmt.Println(err.Error())
    http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
    return
  }
  fmt.Fprintf(w, "Content: %s\n", content)
}
//...

We’ll also need to update our main function to ensure the above two handlers are registered at a path accessible to the browser:

func main() {
  http.HandleFunc("/", handleMain)
  http.HandleFunc("/login", handleFusionAuthLogin)
  http.HandleFunc("/callback", handleFusionAuthCallback)
  fmt.Println(http.ListenAndServe(":8080", nil))
}

Display the user data

In order to fetch and display user info, we need to create the getUserInfo function, as promised above.

//...
func getUserInfo(state string, code string) ([]byte, error) {
  if state != oauthStateString {
    return nil, fmt.Errorf("invalid oauth state")
  }
  token, err := FusionAuthConfig.Exchange(oauth2.NoContext, code)
  if err != nil {
    return nil, fmt.Errorf("code exchange failed: %s", err.Error())
  }
  url := "http://localhost:9011/oauth2/userinfo"
  var bearer = "Bearer " + token.AccessToken
  req, err := http.NewRequest("GET", url, nil)
  req.Header.Add("Authorization", bearer)
  client := &http.Client{}
  response, err := client.Do(req)
  if err != nil {
    return nil, fmt.Errorf("failed getting user info: %s", err.Error())
  }
  defer response.Body.Close()
  contents, err := ioutil.ReadAll(response.Body)
  if err != nil {
   return nil, fmt.Errorf("failed reading response body: %s", err.Error())
  }
  return contents, nil
}
//...

That’s a fair bit of code, let’s break it down:

  • First, we check the state parameter received from the server matches what we sent. This is to prevent CSRF attacks.
  • Then, we exchange the code parameter and retrieve an access token.
  • Next, we construct a request to a well known user information endpoint with an Authorization header, using the access token as a bearer token.
  • Last, we print the response from the server.

If you run this locally, you’ll see something similar to what is in this video:

Next steps

Obviously you’d want to do more than just print out a JSON data structure in a real application. You’d want to show the user’s data, welcome them, and/or show or hide functionality based on who they were.

You could also make requests to other APIs, presenting the access token as a credential.

Conclusion

Using the Authorization Code grant in golang lets you use any OAuth compatible identity provider to secure your application. You can review and fork the application on GitHub.

Happy coding!

Modified golang gopher image courtesy of Renee French, under a CCAv3 license.