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:
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:
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:
Then you’ll need to log in to this new FusionAuth account. This user has administrative privileges within the FusionAuth instance:
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.
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:
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.
Top comments (0)