This is a follow up on my previous post about Google Sign-In. In this post we will discover what to do with the information retrieved in the first encounter, which you can find here: Google Sign-In Part 1.
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 and a session initiated with them. Google nicely enough provided us with some details which we can use. This information was in JSON format and looked something like this:
In my example, to keep things simple, I will use the email address since that has to be unique in the land of Google. You could assign an ID to the user, and you could complicate things even further, but my goal is not to write an academic paper about cryptography here.
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
Once the user used google to provide us with sufficient information about him/herself, we can retrieve data about that user from our records. The data could be anything that is linked to our unique identifier like: Character Profile, Player Information, Status, Last Logged-In, etcetc. 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 simple, returning / new user handling. The concept is simple. If the email isn’t saved, we save it. If it’s saved, we set a logic to our page render to greet the returning user.
AuthHandler I’m doing the following:
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 passed 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 than rendered depending on the
seen boolean like this:
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 authorization. Here, I have to mention that I’m using Gin, so restricted end-points are made with groups which require a middleware.
As I mentioned earlier, I’m using cookies as session handlers. 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)):
gin.Context lets us access this session store by doing
session := sessions.Default(c). Now, create a session variable called
user-id like this:
Don’t forget to
save the session. ;) That is 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
[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 our session is alive, we can use it to restrict access to some part of the application. With Gin, it looks like this:
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 simple, but it gets the job done:
Note, that this only check if
user-id is set or not. That’s certainly not enough for a secure application. Its 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 http://127.0.0.1:9090/battle/field 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 users details in the cookie
- Authorised end-points
- Session handling
Any questions, remarks, ideas, are very welcomed in the comment section. There are plenty of very nice Go frameworks which do Google OAuth2 out of the box. I recommend using them, as they save you a lot of legwork.
Thank you for reading! Gergely.