This is a two part post which explains, with samples, how to do authorization for a web site using Google Authentication OAuth 2.0 services. The first part introduces necessary steps to get started using the Go client provided by Google, and how to setup your application with Google in order to use the API, to retrieve information about the user.

Setup

Google OAuth token

First things first, you need to register your application with Google, so you’ll get a Token that you can use to authorize later calls to Google services. You can do that here: Google Developer Console. You’ll have to create a new project. Once that’s done, click on Credentials and create an OAuth token. You should see this message: “To create an OAuth client ID, you must first set a product name on the consent screen.”. Go through the questions, like, what type of application you have, and once you arrive at the stage where it asks for your application’s name – there is a section asking for a redirect URL; there, write the url you wish to use when authorizing your user. If you don’t know this yet, don’t fret, we can come back and change it later. Do NOT use localhost. If you are running your application on your own machine, use http://127.0.0.1:port/whatever.

This will get you a client ID and a client secret. I’m going to save these into a file which will sit next to my web app. It could be stored more securely, for example, in a database or a mounted secure, encrypted drive, but that’s not the point of this tutorial. Your application can now be identified through Google services.

The Application

Library

Google has a nice library to use with OAuth 2.0. The library is available here: Google OAuth 2.0. It’s a bit cryptic at first to set-up, but not to worry. After a bit of fiddling you’ll quickly understand what it does. I’m also using Gin, and Gin’s session handling middleware Gin-Session.

Setup - Credentials

Let’s create a setup which configures our credentials from the file we saved earlier. This is pretty straightforward.

  1. <b id="docs-internal-guid-bd8655c0-4fc1-c3e9-7039-c2d90d26c835">// Credentials which stores google ids.
  2. type Credentials struct {
  3.    Cid string `json:"cid"`
  4.    Csecret string `json:"csecret"`
  5. }
  6.  
  7. func init() {
  8.    var c Credentials
  9.    file, err := ioutil.ReadFile("./creds.json")
  10.    if err != nil {
  11.        fmt.Printf("File error: %v\n", err)
  12.        os.Exit(1)
  13.    }
  14.    json.Unmarshal(file, &c)
  15. }</b>

Once you have the creds loaded, you can go on to construct the OAuth client.

Setup - OAuth client

Construct the OAuth config:

  1. conf := &oauth2.Config{
  2.   ClientID:     c.Cid,
  3.   ClientSecret: c.Csecret,
  4.   RedirectURL:  "http://localhost:9090/auth",
  5.   Scopes: []string{
  6.     "https://www.googleapis.com/auth/userinfo.email", // You have to select your own scope from here -> https://developers.google.com/identity/protocols/googlescopes#google_sign-in
  7.   },
  8.   Endpoint: google.Endpoint,
  9. }

It will give you a conf struct, which you can then use to Authorize the user in the Google domain. Next, all we need to do is call AuthCodeURL on this config. It will give you a URL which redirects to a Google Sign-In form. Once the user fills that out and clicks ‘Allow’, you’ll get back a TOKEN in the code query parameter and a state which helps protect against CSRF attacks. Always check if the provided state is what you provided with AuthCodeURL. The url will be something like this: http://127.0.0.1:9090/auth?code=4FLKFskdjflf3343d4f&state=lhfu3f983j;asdf. Small function to call AuthCodeURL:

  1. func getLoginURL(state string) string {
  2.     // State can be some random generated hash string.
  3.     // See relevant RFC: http://tools.ietf.org/html/rfc6749#section-10.12
  4.     return conf.AuthCodeURL(state)
  5. }

Construct a button which the user can click and be redirected to the Google Sign-In form. When constructing the url, you must do one more thing. Create a secure state token and save it in the form of a cookie for the current user.

Random State and Button construction

Small piece of code for the random token:

  1. func randToken() string {
  2.     b := make([]byte, 32)
  3.     rand.Read(b)
  4.     return base64.StdEncoding.EncodeToString(b)
  5. }

Storing it in a session and constructing the button:

  1. func loginHandler(c *gin.Context) {
  2.     state = randToken()
  3.     session := sessions.Default(c)
  4.     session.Set("state", state)
  5.     session.Save()
  6.     c.Writer.Write([]byte("<html><title>Golang Google</title> <body> <a href='" + getLoginURL() + "'><button>Login with Google!</button> </a> </body></html>"))
  7. }

It’s not the nicest button I have ever come up with, but for this blog post it will do.

User Information

After you’ve got the token, you can construct an authorised Google HTTP Client, which lets you call Google related services and retrieve information about the user.

Getting the Client

Before we construct a client, we must check if the retrieved state is still the same compared to the one provided. I’m doing this before constructing the client. Together this looks like this:

  1. func authHandler(c *gin.Context) {
  2.     // Check state validity.
  3.     session := sessions.Default(c)
  4.     retrievedState := session.Get("state")
  5.     if retrievedState != c.Query("state") {
  6.         c.AbortWithError(http.StatusUnauthorized, fmt.Errorf("Invalid session state: %s", retrievedState))
  7.         return
  8.     }
  9.     // Handle the exchange code to initiate a transport.
  10.       tok, err := conf.Exchange(oauth2.NoContext, c.Query("code"))
  11.       if err != nil {
  12.           c.AbortWithError(http.StatusBadRequest, err)
  13.           return
  14.       }
  15.     // Construct the client.
  16.     client := conf.Client(oauth2.NoContext, tok)
  17.     ...

Obtaining information

The next step is to retrieve information about the user. Call Google’s API with the authorised client:

  1. ...
  2. resp, err := client.Get("https://www.googleapis.com/oauth2/v3/userinfo")
  3. if err != nil {
  4.     c.AbortWithError(http.StatusBadRequest, err)
  5.     return
  6. }
  7. defer resp.Body.Close()
  8. data, _ := ioutil.ReadAll(resp.Body)
  9. log.Println("Resp body: ", string(data))
  10. ...

The code above will yield a json response like this one:

{
 "sub": "1111111111111111111111",
 "name": "Your Name",
 "given_name": "Your",
 "family_name": "Name",
 "profile": "https://plus.google.com/1111111111111111111111",
 "picture": "https://lh3.googleusercontent.com/asdfadsf/AAAAAAAAAAI/Aasdfads/Xasdfasdfs/photo.jpg",
 "email": "your@gmail.com",
 "email_verified": true,
 "gender": "male"
}

Parse it, and you’ll get an email which you can store somewhere for registration purposes. At this point, your user is not yet Authenticated. My next post will explain how to retrieve the stored email address, and also explain user session handling with Gin and MongoDB.

Putting it all together

The full source code (excluding the templates):

  1. package main
  2.  
  3. import (
  4.     "crypto/rand"
  5.     "encoding/base64"
  6.     "encoding/json"
  7.     "io/ioutil"
  8.     "fmt"
  9.     "log"
  10.     "os"
  11.     "net/http"
  12.  
  13.     "github.com/gin-gonic/contrib/sessions"
  14.     "github.com/gin-gonic/gin"
  15.     "golang.org/x/oauth2"
  16.     "golang.org/x/oauth2/google"
  17. )
  18.  
  19. // Credentials which store google ids.
  20. type Credentials struct {
  21.     Cid     string `json:"cid"`
  22.     Csecret string `json:"csecret"`
  23. }
  24.  
  25. // User is a retrieved and authentiacted user.
  26. type User struct {
  27.     Sub string `json:"sub"`
  28.     Name string `json:"name"`
  29.     GivenName string `json:"given_name"`
  30.     FamilyName string `json:"family_name"`
  31.     Profile string `json:"profile"`
  32.     Picture string `json:"picture"`
  33.     Email string `json:"email"`
  34.     EmailVerified string `json:"email_verified"`
  35.     Gender string `json:"gender"`
  36. }
  37.  
  38. var cred Credentials
  39. var conf *oauth2.Config
  40. var state string
  41. var store = sessions.NewCookieStore([]byte("secret"))
  42.  
  43. func randToken() string {
  44.     b := make([]byte, 32)
  45.     rand.Read(b)
  46.     return base64.StdEncoding.EncodeToString(b)
  47. }
  48.  
  49. func init() {
  50.     file, err := ioutil.ReadFile("./creds.json")
  51.     if err != nil {
  52.         log.Printf("File error: %v\n", err)
  53.         os.Exit(1)
  54.     }
  55.     json.Unmarshal(file, &cred)
  56.  
  57.     conf = &oauth2.Config{
  58.         ClientID:     cred.Cid,
  59.         ClientSecret: cred.Csecret,
  60.         RedirectURL:  "http://127.0.0.1:9090/auth",
  61.         Scopes: []string{
  62.             "https://www.googleapis.com/auth/userinfo.email", // You have to select your own scope from here -> https://developers.google.com/identity/protocols/googlescopes#google_sign-in
  63.         },
  64.         Endpoint: google.Endpoint,
  65.     }
  66. }
  67.  
  68. func indexHandler(c *gin.Context) {
  69.     c.HTML(http.StatusOK, "index.tmpl", gin.H{})
  70. }
  71.  
  72. func getLoginURL(state string) string {
  73.     return conf.AuthCodeURL(state)
  74. }
  75.  
  76. func authHandler(c *gin.Context) {
  77.     // Handle the exchange code to initiate a transport.
  78.     session := sessions.Default(c)
  79.     retrievedState := session.Get("state")
  80.     if retrievedState != c.Query("state") {
  81.         c.AbortWithError(http.StatusUnauthorized, fmt.Errorf("Invalid session state: %s", retrievedState))
  82.         return
  83.     }
  84.  
  85.     tok, err := conf.Exchange(oauth2.NoContext, c.Query("code"))
  86.     if err != nil {
  87.         c.AbortWithError(http.StatusBadRequest, err)
  88.         return
  89.     }
  90.  
  91.     client := conf.Client(oauth2.NoContext, tok)
  92.     email, err := client.Get("https://www.googleapis.com/oauth2/v3/userinfo")
  93.     if err != nil {
  94.         c.AbortWithError(http.StatusBadRequest, err)
  95.         return
  96.     }
  97.     defer email.Body.Close()
  98.     data, _ := ioutil.ReadAll(email.Body)
  99.     log.Println("Email body: ", string(data))
  100.     c.Status(http.StatusOK)
  101. }
  102.  
  103. func loginHandler(c *gin.Context) {
  104.     state = randToken()
  105.     session := sessions.Default(c)
  106.     session.Set("state", state)
  107.     session.Save()
  108.     c.Writer.Write([]byte("<html><title>Golang Google</title> <body> <a href='" + getLoginURL(state) + "'><button>Login with Google!</button> </a> </body></html>"))
  109. }
  110.  
  111. func main() {
  112.     router := gin.Default()
  113.     router.Use(sessions.Sessions("goquestsession", store))
  114.     router.Static("/css", "./static/css")
  115.     router.Static("/img", "./static/img")
  116.     router.LoadHTMLGlob("templates/*")
  117.  
  118.     router.GET("/", indexHandler)
  119.     router.GET("/login", loginHandler)
  120.     router.GET("/auth", authHandler)
  121.  
  122.     router.Run("127.0.0.1:9090")
  123. }

This is it folks. I hope this helped. Any comments and advice are welcome.

Google API Documentation

The documentation of this whole process and MUCH more information can be found here: Google API Docs.

Part 2 coming soon!