2016. november 9.

Authenticating with Google OAuth 2.0 - Part II


This is a follow up to my previous post about doing Google Sign-In in Go, located here. In this post, we will discover what to do with the information retrieved in the first encounter.


The Project

Everything I did in the first post, and that I’m going to do in this example, can be found in this project: Google-OAuth-Go-Sample.

Just to recap, we left off previously on the point where we successfully obtained information about the user, with a secure token. Luckily, Google provided us with some details we can use. This information was in JSON format and looked something like this:

  "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"

In my example, to keep things simple, I will use the email address from the response json, since that has to be unique in the land of Google. You could assign an ID to the user, or you could store multiple things, that’s up to your use case.


Making something useful out of the data

In order for the app to recognise a user it must save some data about the user. I’m doing that in MongoDB right now, but that could be any form of persistence layer, like, SQLite3, BoltDB, PostgresDB, etc.

After successful user authorization

This is the point where we check user credentials, which are stored in our own environment. Things like, Character records, Player IDs, Inventory. For this, there are two things that need to happen after authorization: Save/Load user information and initiate a session.

The session can be in the form of a cookie, or a Redis storage, or URL re-writing. I’m choosing a cookie here.

Save / Load user information

All I’m doing is a ‘returning / new’ user tracking. The concept is: If the email isn’t saved, we save it. If it’s saved, we greet the returning user.
In the AuthHandler I’m doing the following:

  1. ...
  2. seen := false
  3. db := database.MongoDBConnection{}
  4. if _, mongoErr := db.LoadUser(u.Email); mongoErr == nil {
  5.     seen = true
  6. } else {
  7.     err = db.SaveUser(&u)
  8.     if err != nil {
  9.         log.Println(err)
  10.         c.HTML(http.StatusBadRequest, "error.tmpl", gin.H{"message": "Error while saving user. Please try again."})
  11.         return
  12.     }
  13. }
  14. c.HTML(http.StatusOK, "battle.tmpl", gin.H{"email": u.Email, "seen": seen})
  15. ...

Let’s break this down a bit. There is a db connection here, which calls a function that either returns an error, or it doesn’t. If it doesn’t, that means we have our user. If it does, it means we have to save the user. This is a very simple case (disregard for now, that the error could be something else as well. If you can’t get past that, you could type check the error or check if the returned record contains the requested user information instead of checking for an error.).

The template is then rendered depending on the seen boolean like this:

  1. <!DOCTYPE html>
  2. <link rel="icon"
  3.       type="image/png"
  4.       href="/img/favicon.ico" />
  5. <html>
  6.   <head>
  7.     <link rel="stylesheet" href="/css/main.css">
  8.   </head>
  9.   <body>
  10.     {{if .seen}}
  11.         <h1>Welcome back to the battlefield '{{ .email }}'.</h1>
  12.     {{else}}
  13.         <h1>Welcome to the battlefield '{{ .email }}'.</h1>
  14.     {{end}}
  15.   </body>
  16. </html>

You can see here, that if seen is true, the header message will say: “Welcome back…“.

Initiating a session

When the user is successfully authenticated, we activate a session so that the user can access pages that require a logged-in user. Here, I have to mention that I’m using Gin, so restricted end-points are made with groups which require a middleware to be called.

As I mentioned earlier, I’m using cookies for session handling. For this, a new session store has to be created with some secure token. This is achieved with the following code fragments (note that I’m using a Gin session middleware which uses gorilla’s session handler located here: Gin-Gonic (Sessions):

  1. // RandToken in handlers.go:
  2. // RandToken generates a random @l length token.
  3. func RandToken(l int) string {
  4.     b := make([]byte, l)
  5.     rand.Read(b)
  6.     return base64.StdEncoding.EncodeToString(b)
  7. }
  9. // quest.go:
  10. // Create the cookie store in quest.go.
  11. store := sessions.NewCookieStore([]byte(handlers.RandToken(64)))
  12. store.Options(sessions.Options{
  13.     Path:   "/",
  14.     MaxAge: 86400 * 7,
  15. })
  17. // using the cookie store:
  18. router.Use(sessions.Sessions("goquestsession", store))

After this, gin.Context lets us access this session store by doing session := sessions.Default(c). Now, create a session variable called user-id like this:

  1. session.Set("user-id", u.Email)
  2. err = session.Save()
  3. if err != nil {
  4.     log.Println(err)
  5.     c.HTML(http.StatusBadRequest, "error.tmpl", gin.H{"message": "Error while saving session. Please try again."})
  6.     return
  7. }

Don’t forget to save the session. That’s it. If I restart the server, the cookie won’t be usable any longer, since it will generate a new token for the cookie store. The user will have to log in again. Note: It might be that you’ll see something like this, from session: [sessions] ERROR! securecookie: the value is not valid. You can ignore this error.

Restricting access to certain end-points with the auth Middleware™

Now, that we have a session, we can use it to restrict access to some part of the application. With Gin, it looks like this:

  1. authorized := router.Group("/battle")
  2. authorized.Use(middleware.AuthorizeRequest())
  3. {
  4.     authorized.GET("/field", handlers.FieldHandler)
  5. }

This creates a grouping of end-points under /battle. Which means, everything under /battle will only be accessible if the middleware passed to the Use function calls the next handler in the chain. If it aborts the call chain, the end-point will not be accessible. My middleware is pretty basic, but it gets the job done:

  1. // AuthorizeRequest is used to authorize a request for a certain end-point group.
  2. func AuthorizeRequest() gin.HandlerFunc {
  3.     return func(c *gin.Context) {
  4.         session := sessions.Default(c)
  5.         v := session.Get("user-id")
  6.         if v == nil {
  7.             c.HTML(http.StatusUnauthorized, "error.tmpl", gin.H{"message": "Please log in."})
  8.             c.Abort()
  9.         }
  10.         c.Next()
  11.     }
  12. }

Note, that this only checks if user-id is set or not. That’s certainly not enough for a secure application. It’s only supposed to be a simple example of the mechanics of the auth middleware. Also, the session usually contains more than one parameter. It’s more likely that it contains several variables, which describe the user including a state for CORS protection. For CORS I’d recommend using rs/cors.

If you would try to access without logging in, you’d be redirected to an error.tmpl with the message: Please log in...

That’s pretty much it. Important parts are:

  • Saving the right information
  • Secure cookie store
  • CORS for sessions
  • Checks of the user’s details in the cookie
  • Authorised end-points
  • Session handling

Thanks for reading!

Related posts

2019. április 25.

One of our clients required an image of a map for print. As will be seen on the map, there will be marked places that represent where the company performed surveys.

2016. augusztus 3.

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.