After learning about the new Go language, I immediately wanted to try creating something with it. I started a web app and learned about the Gin framework. While the router in the Go standard library is nice, Gin adds a few nice to haves and as well as a performance boost.

When I started adding a user system to the app, I knew I wanted users to be able to sign in with OAuth. The process is too convenient from a user’s perspective not to. I found the Goth library which makes using OAuth very easy and supports a very large number of providers.

I ran into a couple of problems when trying to use Gin and Goth together. Here is what it took to get them up and running.

Setting up the store

I ran into a few problems when Goth tried to use its store. The official documentation describes there being a store by default. I ended up having to manually set a store. Repo comments suggest the code had been changed to minimize its dependency on gorilla/sessions but the documentation was not yet updated. Here is how I manually set the store.

1
2
3
4
5
6
7
8
key := os.Getenv("SESSION_KEY")
store := sessions.NewCookieStore([]byte(key))
store.MaxAge(86400 * 30) // 30 days
store.Options.Path = "/"
store.Options.HttpOnly = true
store.Options.Secure = false

gothic.Store = store

Your SESSION_KEY should be generated using crypto/rand or securcookie.GenerateRandomKey(32) and saved to an OS variable to be read in. You should not save you key within your source code.

Adding providers

Goth needs to be told about the potential providers it should be looking for. It supports a large number of providers but I will only cover Google in this example. For a full list of providers, check out the official Goth documentation.

You will need to get credentials for your application through your provider. With Google this can be done in the credentials section of the Google Cloud Platform. You will also need to tell the provider what ip addresses to accept requests from and where to redirect the user once finished. While developing, I set requests from http://localhost:8080 and sent users back to http://localhost:8080/auth/google/callback

These values should also be saved as an OS variable so they are not checked into source control. This can be done inside your program to temporarily test your code.

1
2
os.Setenv("GOOGLE_KEY", <Your key>)
os.Setenv("GOOGLE_SECRET", <Your secret>)

Next is actually tell Goth what providers and their associated credentials. Multiple providers can be added in a single statement.

1
2
3
goth.UseProviders(
  google.New(os.Getenv("GOOGLE_KEY"), os.Getenv("GOOGLE_SECRET"), "http://localhost:8080/auth/google/callback"),
)

Routing the Requests

You will need to tell Gin where to route requests. Rather than creating a handler for each provider, Gin’s features in path parameters that can be used to create generic handlers. There are a couple of different ways that the provider type can be sent to the handler function.

The first method will pass the provider through a query string. This requires the sending link to include this information.

1
2
3
4
router.GET("/auth/:provider/", controllers.BeginAuth)

<!-- HTML -->
<a href="/auth/google/?provider=google">Login through google</a>

The second method will pass the provider using path parameters. This will require the provider to be pulled out of the URL and added the query string inside the handler. The response I received from Google required this method.

1
router.GET("/auth/:provider/callback/", AuthCallback)

Handling the Requests

The first handler function is the BeginAuth function. This function starts the authentication process. Not much here, just make a call to gothic.BeginAuthHandler to start the authentication process.

1
2
3
func Auth(cntx *gin.Context) {
    gothic.BeginAuthHandler(cntx.Writer, cntx.Request)
}

There are a few more steps in the AuthCallback function. This is the code that will be called once a response is received from the provider.

The first lines are to make sure the provider is in the query string. These lines could also be added to the Auth function if you would rather pass path parameters instead of a query string.

There is then a call to CompleteUserAuth. This uses the provider added to the query string to determine which provider the response is from. It then processes the response and returns a goth.User struct with the user’s information.

The last step will be to direct the user to the desired results page. This could be a separate page to show success or back your app’s home page.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
func AuthCallback(cntx *gin.Context) {

    // Pull the provider from the path parameters
    query := cntx.Request.URL.Query()
    query.Add("provider", cntx.Param("provider"))
    cntx.Request.URL.RawQuery = query.Encode()

    gothic.SetState(cntx.Request)

    // Call CompleteUserAuth to have it process the response
    gothUser, err := gothic.CompleteUserAuth(cntx.Writer, cntx.Request)
    if err != nil {
        log.Println(err)
        return
    }

    // Save the needed information from the response

    cntx.HTML(http.StatusOK, "success", nil)
}

Running through the process

You should now be able to test the entire system worked by clicking on your link. The first time through you will be asked to allow access on the provider’s page. It will then route you back to the specified callback path.