> For the complete documentation index, see [llms.txt](https://fusionauth.io/docs/llms.txt)

# FusionAuth QuickStart for Golang

QuickStart integration of a Golang web application with FusionAuth using the CoreOS OIDC library.

In this QuickStart, you are going to build an application with Go and integrate it with FusionAuth. You'll be building it for [ChangeBank](https://www.youtube.com/watch?v=CXDxNCzUspM), a global leader in converting dollars into coins. It'll have areas reserved for users who have logged in as well as public facing sections.

The Docker Compose file and source code for a complete application are available at [https://github.com/FusionAuth/fusionauth-quickstart-golang-web](https://github.com/FusionAuth/fusionauth-quickstart-golang-web).

## Prerequisites

*   [Go v1.16](https://go.dev/doc/install)
*   [Docker](https://www.docker.com): The quickest way to stand up FusionAuth. (There are [other ways](https://fusionauth.io/docs/get-started/download-and-install.md)).
*   On macOS and Windows, one of the following container management tools:
    *   [Docker desktop](https://www.docker.com/products/docker-desktop/)
    *   [OrbStack](https://docs.orbstack.dev/quick-start) (to use Orbstack for `docker compose` commands after install, run `docker context use orbstack`)
    *   [Podman](https://podman.io/docs/installation) (in the commands below, replace `docker` with `podman`)

## General Architecture

Here's a typical application login flow before FusionAuth:

```mermaid
sequenceDiagram
    participant User
    participant App as Application

    User ->> App : View Homepage
    User ->> App : Click Login Link
    App ->> User : Show Login Form
    User ->> App : Fill Out and Submit Login Form
    App ->> App : Authenticates User
    App ->> User : Display User's Account or Other Info
```

Request flow during login before FusionAuth

And here's the same application login flow after introducing FusionAuth:

```mermaid
sequenceDiagram
    participant User
    participant App as Application
    participant FusionAuth

    User ->> App : View Homepage
    User ->> App : Click Login Link (to FusionAuth)
    User ->> FusionAuth : View Login Form
    FusionAuth ->> User : Show Login Form
    User ->> FusionAuth : Fill Out and Submit Login Form
    FusionAuth ->> FusionAuth : Authenticates User
    FusionAuth ->> User: Go to Redirect URI
    User ->> App: Request the Redirect URI
    App ->> FusionAuth : Is User Authenticated?
    FusionAuth ->> App : User is Authenticated
    App ->> User : Display User's Account or Other Info
```

Request flow during login after FusionAuth

In general, you are introducing FusionAuth in order to normalize and consolidate user data. This helps make sure it is consistent and up-to-date as well as offloading your login security and functionality to FusionAuth.

## Getting Started

In this section, you'll get FusionAuth up and running, and configured with the ChangeBank application.

### Clone the Code

First off, grab the code from the repository and change into that directory.

```shell-session
$ git clone https://github.com/FusionAuth/fusionauth-quickstart-golang-web.git
```

```shell-session
$ cd fusionauth-quickstart-golang-web
```

### Run FusionAuth via Docker

You'll find a Docker Compose file (`docker-compose.yml`) and an environment variables configuration file (`.env`) in the root directory of the repo.

Assuming you have Docker installed, you can stand up FusionAuth on your machine with the following.

```shell-session
docker compose up -d
```

Here you are using a bootstrapping feature of FusionAuth called [Kickstart](https://fusionauth.io/docs/get-started/download-and-install/development/kickstart.md). When FusionAuth comes up for the first time, it will look at the `kickstart/kickstart.json` file and configure FusionAuth to your specified state.

> **NOTE:** If you ever want to reset the FusionAuth application, you need to delete the volumes created by Docker Compose by executing `docker compose down -v`, then re-run `docker compose up -d`.

FusionAuth will be initially configured with these settings:

*   Your client Id is `e9fdb985-9173-4e01-9d73-ac2d60d1dc8e`.
*   Your client secret is `2HYT86lWSAntc-mvtHLX5XXEpk9ThcqZb4YEh65CLjA-not-for-prod`.
*   Your example username is `richard@example.com` and the password is `password`.
*   Your admin username is `admin@example.com` and the password is `password`.
*   The base URL of FusionAuth is `http://localhost:9011/`.

You can log in to the [FusionAuth admin UI](http://localhost:9011/admin) and look around if you want to, but with Docker and Kickstart, everything will already be configured correctly.

> **NOTE:** If you want to see where the FusionAuth values came from, they can be found in the [FusionAuth app](http://localhost:9011/admin). The tenant Id is found on the Tenants page. To see the Client Id and Client Secret, go to the Applications page and click the `View` icon under the actions for the ChangeBank application. You'll find the Client Id and Client Secret values in the `OAuth configuration` section.

> **CAUTION:** The `.env` file contains passwords. In a real application, always add this file to your `.gitignore` file and never commit secrets to version control.

### Create a Basic Golang Application

In this section, you'll set up a basic Golang application with a single page.

#### Setup Your Environment

Create a new directory to hold your application, and go into it.

```shell-session
$ mkdir changebank && cd changebank
```

Create a `go.mod` file listing these dependencies:

*go.mod*

```go
module main

go 1.16

require (
	github.com/coreos/go-oidc/v3 v3.6.0
	github.com/thanhpk/randstr v1.0.6
	golang.org/x/oauth2 v0.8.0
)
```

#### Create the Application

Now create a base Go app, which will consist of a single file named main.go. You can either copy the code shown here, or you can copy the file `/complete-application/base-app.go` from the QuickStart repo and name it `main.go`.

This app sets up a handful of routes that just serve a home page for now. Over the course of this QuickStart you'll be modifying each of these route handler functions to complete the integration with FusionAuth.

*main.go*

```go
package main

import (
  "fmt"
  "html/template"
  "net/http"
  "path"
)

func main() {
  http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("static"))))
  http.HandleFunc("/", handleMain)
  http.HandleFunc("/login", handleFusionAuthLogin)
  http.HandleFunc("/callback", handleFusionAuthCallback)
  http.HandleFunc("/account", handleAccount)
  http.HandleFunc("/make-change", handleMakeChange)
  http.HandleFunc("/logout", handleLogout)

  port := "8080"

  fmt.Println("Starting HTTP server at http://localhost:" + port)
  fmt.Println(http.ListenAndServe(":" + port, nil))
}

func handleMain(w http.ResponseWriter, r *http.Request) {
  WriteWebPage(w, "home.html", nil)
  return
}

func handleFusionAuthLogin(w http.ResponseWriter, r *http.Request) {
  http.Redirect(w, r, "/", http.StatusFound)
}

func handleFusionAuthCallback(w http.ResponseWriter, r *http.Request) {
  http.Redirect(w, r, "/", http.StatusFound)
}

func handleAccount(w http.ResponseWriter, r *http.Request) {
  http.Redirect(w, r, "/", http.StatusFound)
}

func handleMakeChange(w http.ResponseWriter, r *http.Request) {
  http.Redirect(w, r, "/", http.StatusFound)
}

func handleLogout(w http.ResponseWriter, r *http.Request) {
}

func WriteWebPage(w http.ResponseWriter, tmpl string, vars interface{}) {
  fn := path.Join("templates", tmpl)
  parsedTmpl, err := template.ParseFiles(fn)

  if err != nil {
    http.Error(w, "Error reading template file " + tmpl + ": " + err.Error(), http.StatusInternalServerError)
    return
  }

  if err := parsedTmpl.Execute(w, vars); err != nil {
    http.Error(w, err.Error(), http.StatusInternalServerError)
  }
}

func WriteCookie(w http.ResponseWriter, name string, value string, maxAge int, httpOnly bool) {
  cookie := http.Cookie{
    Name:     name,
    Domain:   "localhost",
    Value:    value,
    Path:     "/",
    MaxAge:   maxAge,
    HttpOnly: httpOnly,
    SameSite: http.SameSiteLaxMode,
  }
  http.SetCookie(w, &cookie)
}
```

And then load your dependencies and generate go.sum

```shell-session
$ go mod download all
```

#### Run the App!

You should now be able to start your Go application with

```shell-session
$ go run main.go
```

Note that you won't be able to access it with a browser, since you haven't created any pages yet.

#### Create a Home Page

The next step is to get a basic home page up and running. We'll take this opportunity to copy in all of the static assets that you'll need for the application, including web page templates, images, and CSS.

Copy the `templates` and `static` directories from the `complete-application` directory in the cloned QuickStart repo into your `changebank` project directory.

Here's what the home page template looks like:

*templates/home.html*

```html
<html>
<head>
  <meta charset="utf-8" />
  <title>FusionAuth Golang example</title>
  <link rel="stylesheet" href="/static/css/changebank.css">
</head>
<body>
  <div id="page-container">
    <div id="page-header">
      <div id="logo-header">
        <img src="https://fusionauth.io/cdn/samplethemes/changebank/changebank.svg" />
        <a class="button-lg" href="/login">Login</a>
      </div>

      <div id="menu-bar" class="menu-bar">
        <a class="menu-link">About</a>
        <a class="menu-link">Services</a>
        <a class="menu-link">Products</a>
        <a class="menu-link" style="text-decoration-line: underline;">Home</a>
      </div>
    </div>

    <div style="flex: 1;">
      <div class="column-container">
        <div class="content-container">
          <div style="margin-bottom: 100px;">
            <h1>Welcome to Changebank</h1>
            <p>To get started, <a href="/login">log in or create a new account</a>.</p>
          </div>
        </div>
        <div style="flex: 0;">
          <img src="/static/img/money.jpg" style="max-width: 800px;"/>
        </div>
      </div>
    </div>
</body>
</html>
```

And the stylesheet for the application:

*static/css/changebank.css*

```css
h1 {
  color: #096324;
}

h3 {
  color: #096324;
  margin-top: 20px;
  margin-bottom: 40px;
}

a {
  color: #096324;
}

p {
  font-size: 18px;
}

.header-email {
  color: #096324;
  margin-right: 20px;
}

.fine-print {
  font-size: 16px;
}

body {
  font-family: sans-serif;
  padding: 0px;
  margin: 0px;
}

.h-row {
  display: flex;
  align-items: center;
}

#page-container {
  display: flex;
  flex-direction: column;
  width: 100%;
  height: 100%;
}

#page-header {
  flex: 0;
  display: flex;
  flex-direction: column;
}

#logo-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 10px;
}

.menu-bar {
  display: flex;
  flex-direction: row-reverse;
  align-items: center;
  height: 35px;
  padding: 15px 50px 15px 30px;
  background-color: #096324;
  font-size: 20px;
}

.menu-link {
  font-weight: 600;
  color: #FFFFFF;
  margin-left: 40px;
}

.inactive {
  text-decoration-line: none;
}

.button-lg {
  width: 150px;
  height: 30px;
  background-color: #096324;
  color: #FFFFFF;
  font-size: 16px;
  font-weight: 700;
  border-radius: 10px;
  text-align: center;
  padding-top: 10px;
  text-decoration-line: none;
}

.column-container {
  display: flex;
  flex-direction: row;
  justify-content: space-between;
}

.content-container {
  flex: 1;
  display: flex;
  flex-direction: column;
  padding: 60px 20px 20px 40px;
}

.balance {
  font-size: 50px;
  font-weight: 800;
}

.change-label {
  font-size: 20px;
  margin-right: 5px;
}

.change-input {
  font-size: 20px;
  height: 40px;
  text-align: end;
  padding-right: 10px;
}

.change-submit {
  font-size: 15px;
  height: 40px;
  margin-left: 15px;
  border-radius: 5px;
}

.change-message {
  font-size: 20px;
  margin-bottom: 15px;
}

.error-message {
  font-size: 20px;
  color: #FF0000;
  margin-bottom: 15px;
}

.app-container {
  flex: 0;
  min-width: 440px;
  display: flex;
  flex-direction: column;
  margin-top: 40px;
  margin-left: 80px;
}

.change-container {
  flex: 1;
}
```

> **TIP:** With the home page template in place, you can view the home page in your browser at [http://localhost:8080](http://localhost:8080).

## Authentication

In this section, you'll add the ability for a user to log in to your application using FusionAuth as the identity provider. To accomplish this, you'll do the following.

*   Configure an OIDC client
*   Modify the `/login` route to redirect the user to FusionAuth to log in
*   Add code to the `/callback` route to accept the redirect from FusionAuth and to exchange an authorization code for an access token
*   Add protection to the `/account` route to only allow access by logged-in users
*   Modify the `/` route to detect if a user is logged in, and take them to `/account` when they are
*   Add a `/logout` endpoint

### Configure the OIDC Client

The CoreOS OpenID Connect client needs to be set up so that it knows how to talk to FusionAuth.

First, update your imports to include the OIDC and OAuth2 packages:

```go
import (
  "fmt"
  "html/template"
  "math"
  "net/http"
  "net/url"
  "path"
  "strconv"

  "github.com/coreos/go-oidc/v3/oidc"
  "github.com/thanhpk/randstr"
  "golang.org/x/oauth2"
)
```

Next, create some constants after your `import` block and before `func main()`:

```go
const (
  FusionAuthHost         string = "http://localhost:9011"
  FusionAuthTenantID     string = "d7d09513-a3f5-401c-9685-34ab6c552453"
  FusionAuthClientID     string = "e9fdb985-9173-4e01-9d73-ac2d60d1dc8e"
  FusionAuthClientSecret string = "2HYT86lWSAntc-mvtHLX5XXEpk9ThcqZb4YEh65CLjA-not-for-prod"
  AccessTokenCookieName  string = "cb_access_token"
  RefreshTokenCookieName string = "cb_refresh_token"
  IDTokenCookieName      string = "cb_id_token"
)
```

> **NOTE:** If you want to see where the FusionAuth values came from, they can be found in the FusionAuth app ([http://localhost:9011/admin](http://localhost:9011/admin)). The tenant ID is found on the Tenants page. To see the client ID and client secret, go to the Applications page and click the View icon under the actions for the Changebank application. You'll find the client id and client secret values in the OAuth configuration section.

Next, create variables for the OAuth config and the OIDC provider, and initialize them in an `init()` function. Put this code after the `const` block you just added.

```go
var (
  oidcProvider     *oidc.Provider
  fusionAuthConfig *oauth2.Config

  // In a production application, persist a unique state string per login request
  oauthStateString string = randstr.Hex(16)
)

func init() {
  provider, err := oidc.NewProvider(oauth2.NoContext, FusionAuthHost)

  if err != nil {
    fmt.Println("Error creating OIDC provider: " + err.Error())
  } else {
    oidcProvider = provider

    fusionAuthConfig = &oauth2.Config{
      ClientID:     FusionAuthClientID,
      ClientSecret: FusionAuthClientSecret,
      RedirectURL:  "http://localhost:8080/callback",
      Endpoint:     oidcProvider.Endpoint(),
      Scopes:       []string{oidc.ScopeOpenID, "email", "offline_access"},
    }
  }
}
```

### Modify the `/login` Route

When the user clicks the Login button in your Changebank app, they'll be taken to your `/login` endpoint. Have this redirect them to FusionAuth so that FusionAuth can present them with a login page. Change the `handleLoginRequest` function to do that.

```go
func handleLoginRequest(w http.ResponseWriter, r *http.Request) {
  http.Redirect(w, r, fusionAuthConfig.AuthCodeURL(oauthStateString), http.StatusFound)
}
```

The Login button on your home page will now take you to FusionAuth to log in. You can log in, but nothing will happen after you do until you add the callback handler in the next section.

### Handle the OAuth Callback

After the user successfully authenticates with FusionAuth, FusionAuth will redirect the user back to your application along with an authorization code. Your application needs to exchange this code for an access token using FusionAuth's token endpoint. This is the defining action of the OAuth code grant flow. An access token can only be acquired for a user by an application that has a valid authorization code, as well as a valid client ID and client secret.

Modify the `handleFusionAuthCallback` function to do this.

```go
func handleFusionAuthCallback(w http.ResponseWriter, r *http.Request) {

  // Validate the state value to make sure this came from us
  if r.FormValue("state") != oauthStateString {
    http.Error(w, "Bad request - incorrect state value", http.StatusBadRequest)
    return
  }

  // Exchange the authorization code for access, refresh, and id tokens
  token, err := fusionAuthConfig.Exchange(oauth2.NoContext, r.FormValue("code"))

  if err != nil {
    http.Error(w, "Error getting access token: "+err.Error(), http.StatusInternalServerError)
    return
  }

  rawIDToken, ok := token.Extra("id_token").(string)

  if !ok {
    http.Error(w, "No ID token found in request to /callback", http.StatusBadRequest)
    return
  }

  // Write access, refresh, and id tokens to http-only cookies
  WriteCookie(w, AccessTokenCookieName, token.AccessToken, 3600, true)
  WriteCookie(w, RefreshTokenCookieName, token.RefreshToken, 3600, true)
  WriteCookie(w, IDTokenCookieName, rawIDToken, 3600, false)

  http.Redirect(w, r, "/account", http.StatusFound)
}
```

This function writes three tokens out to HTTP-only cookies. This means they aren't available to code running in the browser, but they'll be sent back to your Go application when requests are made to the back end.

### Create a Protected Web Page

The `/account` page represents what a user sees when they're logged into their Changebank account. A user that isn't logged in, who tries to access this page, should just be taken to the home page.

Edit the `handleAccount()` function to do this. There is also a `getLogoutUrl()` function that you'll need to add.

```go
func getLogoutUrl() string {
  return fmt.Sprintf("%s/oauth2/logout?client_id=%s&tenantId=%s",
    FusionAuthHost, url.QueryEscape(FusionAuthClientID), url.QueryEscape(FusionAuthTenantID))
}

func handleAccount(w http.ResponseWriter, r *http.Request) {

  // Make sure the user is authenticated. In a production application, validate the token
  // signature, check expiration, and attempt to refresh if expired.
  cookie, err := r.Cookie(AccessTokenCookieName)

  if err != nil || cookie == nil {
    http.Redirect(w, r, "/", http.StatusFound)
    return
  }

  // Get the ID token to display the user's email address
  cookie, err = r.Cookie(IDTokenCookieName)

  if err != nil || cookie == nil {
    http.Error(w, "No ID token found", http.StatusBadRequest)
    return
  }

  verifier := oidcProvider.Verifier(&oidc.Config{ClientID: FusionAuthClientID})

  idToken, err := verifier.Verify(oauth2.NoContext, cookie.Value)

  if err != nil {
    http.Error(w, "Error verifying ID token: "+err.Error(), http.StatusBadRequest)
    return
  }

  // Extract the email claim
  var claims struct {
    Email string `json:"email"`
  }

  if err := idToken.Claims(&claims); err != nil {
    http.Error(w, "Error reading claims from ID token: "+err.Error(), http.StatusInternalServerError)
    return
  }

  WriteWebPage(w, "account.html", AccountVars{LogoutUrl: getLogoutUrl(), Email: claims.Email})
}
```

Here's the account page template:

`templates/account.html`

```html
<html>
<head>
  <meta charset="utf-8" />
  <title>FusionAuth Golang example</title>
  <link rel="stylesheet" href="/static/css/changebank.css">
</head>
<body>
  <div id="page-container">
    <div id="page-header">
      <div id="logo-header">
        <img src="https://fusionauth.io/cdn/samplethemes/changebank/changebank.svg" />
        <div class="h-row">
          <p class="header-email">{{ .Email }}</p>
          <a class="button-lg" href="{{ .LogoutUrl }}">Logout</a>
        </div>
      </div>

      <div id="menu-bar" class="menu-bar">
        <a class="menu-link inactive" href="/make-change">Make Change</a>
        <a class="menu-link" href="/account">Account</a>
      </div>
    </div>

    <div style="flex: 1;">
      <div class="column-container">
        <div class="app-container">
          <h3>Your balance</h3>
          <div class="balance">$0.00</div>
        </div>
      </div>
    </div>
</body>
</html>
```

> **TIP:** At this point, you should be able to successfully log into your application!

### Change the Home Route

Next you'll modify the `handleMain()` function to automatically take a logged-in user to their account page.

```go
func handleMain(w http.ResponseWriter, r *http.Request) {
  // See if the user is authenticated. In a real application, validate the token signature and expiration.
  _, err := r.Cookie(AccessTokenCookieName)

  if err != nil {
    WriteWebPage(w, "home.html", nil)
    return
  }

  // The user is authenticated, redirect to /account.
  http.Redirect(w, r, "/account", http.StatusFound)
  return
}
```

### Implement Logout

The last step is to implement logout. When you log a user out of an application, you'll take them to FusionAuth's `/oauth2/logout` endpoint. After logging the user out, FusionAuth will redirect the user to your app's `/logout` endpoint, which you'll create now. This endpoint deletes any cookies that your application created, clearing the user's session.

Update the `handleLogout()` function to do this.

```go
func handleLogout(w http.ResponseWriter, r *http.Request) {
  // Delete the cookies we set
  WriteCookie(w, AccessTokenCookieName, "", -1, true)
  WriteCookie(w, RefreshTokenCookieName, "", -1, true)
  WriteCookie(w, IDTokenCookieName, "", -1, false)

  http.Redirect(w, r, "/", http.StatusFound)
}
```

> **TIP:** Click the Logout button and watch the browser first go to FusionAuth to log out the user, then return to your home page.

## Next Steps

This QuickStart is a great way to get a proof of concept up and running quickly, but to run your application in production, there are some things you're going to want to do.

### FusionAuth Customization

FusionAuth gives you the ability to customize just about everything to do with the user's experience and the integration of your application. This includes:

*   [Hosted pages](https://fusionauth.io/docs/customize/look-and-feel.md) such as login, registration, email verification, and many more.
*   [Email templates](https://fusionauth.io/docs/customize/email-and-messages/email-templates.md).
*   [User data and custom claims in access token JWTs](https://fusionauth.io/articles/tokens/jwt-components-explained.md).

### Security

*   You may want to customize the [token expiration times and policies](https://fusionauth.io/docs/lifecycle/authenticate-users/oauth.md) in FusionAuth.
*   Choose [password rules](https://fusionauth.io/docs/get-started/core-concepts/tenants.md#password) and a [hashing algorithm](https://fusionauth.io/docs/reference/password-hashes.md) that meet your security needs.

### Tenant and Application Management

*   Model your application topology using [Applications](https://fusionauth.io/docs/get-started/core-concepts/applications.md), [Roles](https://fusionauth.io/docs/get-started/core-concepts/roles.md), [Groups](https://fusionauth.io/docs/get-started/core-concepts/groups.md), [Entities](https://fusionauth.io/docs/get-started/core-concepts/groups.md), and more.
*   Set up [MFA](https://fusionauth.io/docs/lifecycle/authenticate-users/multi-factor-authentication.md), [Social login](https://fusionauth.io/docs/lifecycle/authenticate-users/identity-providers.md), or [SAML](https://fusionauth.io/docs/lifecycle/authenticate-users/identity-providers/overview-samlv2.md) integrations.
*   Integrate with external systems using [Webhooks](https://fusionauth.io/docs/extend/events-and-webhooks.md), [SCIM](https://fusionauth.io/docs/lifecycle/migrate-users/scim.md), and [Lambdas](https://fusionauth.io/docs/extend/code/lambdas.md).