Building A Simple Job-Position Site In GoBuffalo part 3

Connel Asikong
6 min readNov 22, 2017

Here’s part one
Here’s part two

The difference between websites and webapps is that the app displays dynamic data, while a simple site can present some static values. Although in this example we’ve started working with database and generating dynamic contents, so it’s now a web application. Congratulations to you, you have done it and have saved the web masters but we’re all hiding from a monster(cyber-attack) with the doors closed not locked. Anybody can edit what’s on the site if the find the site, so super-man what do we do?

I am Batman with the idea. Let’s add a not-too-complicated authentication system using external providers (Github) & (Twitter) to restrict access to this site.

Authentication

When we generated a handler for jobs, it was added to actions/app.go :

// actions/app.go
func App() *buffalo.App {
if app == nil {
...
app.GET("/", HomeHandler)
app.GET("/position", PositionHandler)
app.GET("/aboutUs", AboutHandler)

app.ServeFiles("/assets", packr.NewBox("../public/assets"))
app.Resource("/jobs", JobsResource{&buffalo.BaseResource{}})
}
return app
}

What’s happening here is that a new path group is created under /jobs and some endpoints were added to it, such as listing all jobs, adding a new one, showing a particular one, etc. App.Group gives room to add a specific middleware, without having it in use for all other paths. This is perfect for our use case because we don't want the users to authenticate to see the public part of the site, but the should do it if they want to edit the content of the site.

Goth

Authentication is a common thing, and Buffalo has a generator for it. The tool that is used for all the heavy lifting is goth, and it provides a long list of external auth providers including (cb is typing…).
In this our little walk, we’ll be using Github & Twitter. By default, gothic uses a CookieStore from the gorilla/sessions package to store session data.
To start we’ll type a command to generate stuffs:
go get github.com/as27/setenv
We are going to use setenv to hold our ID’s and Keys.
Create a file name auth.go in actions/ and create a file .env to store the keys and ID’s of your app (but you can call it any name). Let’s add some codes in actions/app.go

auth := app.Group("/auth")
wrapper := buffalo.WrapHandlerFunc(gothic.BeginAuthHandler)
auth.GET("/{provider}", wrapper)
auth.GET("/{provider}/callback", AuthCallback)
app.Resource("/jobs", JobsResource{&buffalo.BaseResource{}})

Two endpoints have been added to our application, but we will have to look inside actions/auth.go to see what happens under cover to fully understand what they’re:

package actions

...

func init() {
gothic.Store = App().SessionStore

setenv.File(".env")

goth.UseProviders(
github.New(os.Getenv("GITHUB_KEY"), os.Getenv("GITHUB_SECRET"), fmt.Sprintf("%s%s", App().Host, "/auth/github/callback")),
twitter.New(os.Getenv("TWITTER_KEY"), os.Getenv("TWITTER_SECRET"), fmt.Sprintf("%s%s", App().Host, "/auth/twitter/callback")),
)
}

func AuthCallback(c buffalo.Context) error {
user, err := gothic.CompleteUserAuth(c.Response(), c.Request())
if err != nil {
return c.Error(401, err)
}
// Do something with the user, register them/sign them in
return c.Render(200, r.JSON(user))
}

First of all, we have Github & Twitter integration added to a list of providers available to goth in an init() function. Take a look at app.go file, you’ll see that an authentication endpoint calls gothic.BeginAuthHandler function with a provider parameter sent in a path. We would enter it through /auth/github& /auth/twitter, so that our provider will be chosen. What it does is redirects the user to an external provider login page, where they will be authorized in order to proceed.

Before we continue, let’s create our apps in Github and Twitter.
First Github:

click on settings in your github account
click on developer settings
click on new OAuth app

take note of Homepage URL and Authorization callback URL

You’ll be given Client ID and Client Secret. Copy it and save it into a file called .env in root directory.
Now Twitter:
Visit link then create your application there and get the ID and Key.
Now let’s get this working, is it magic? hell naw!~maybe :)

After user authorization on Github/Twitter, they need to be redirected back to our app. In order to do that, Github/Twitter needs to know where should they send our users. This second endpoint is defined in actions/auth.go:
func AuthCallback(c buffalo.Context) error{} function.

look at that; what golang is doing to others :)

Okay back to programming…

It prints a JSON sent by the provider. Anyway, we need to add a path to that callback endpoint on Github & Twitter side. If you set this up, you can finally authenticate yourself on the application. If you enter /auth/github/ /auth/twitter, you get redirected to Github/Twitter:

redirected to github
redirected to twitter

When we authorize the app, we are sent back to our landing page. Congratulation, i think my work here is done. Oh wait! Can’t we try to make it like all these sweet sites that when you login, you are redirected to a different page? yes we can.

Middleware

At this point in this tutorial, we should process the profile information, create some user profile of our own and bind those two together. But hey! this is a green lantern not a Justice League meaning it’s a simple tutorial. Therefore, we’ll settle for a super-naive implementation, where we’ll just add a value to the session and check if it’s present. If present, the user can access restricted pages on the site. If not, they’ll get redirected to login page.

We introduce a middleware function that checks a value from context’s Session and does proper redirects. Another step would be to redirect users to a correct page after login. We don’t want them to see their profile JSON, but access the page they requested before being redirected to Github/Twitter. To do that let’s set another property in Session (called redirectTo):

// actions/auth.go
func
SetCurrentUser() buffalo.MiddlewareFunc {
return func(h buffalo.Handler) buffalo.Handler {
return func(c buffalo.Context) error {
t := c.Session().Get("key")
if t == nil {
// set the redirect URL
c.Session().Set("redirectTo", c.Request().URL.String())
return c.Redirect(http.StatusFound, "/")
}
return h(c)
}
}
}

Now we’ll need to add this key on authentication callback. We can use Session.GetOnce(..) function which removes the value after reading it, since we won't be needing it anymore:

func AuthCallback(c buffalo.Context) error {
user, err := gothic.CompleteUserAuth(c.Response(), c.Request())
if err != nil {
return c.Error(401, err)
}
c.Session().Set("key", user.AccessToken)

rTo := c.Session().GetOnce("redirectTo")
rToStr, ok := rTo.(string)
if !ok || rToStr == "" {
rToStr = "/jobs"
}
return c.Redirect(http.StatusFound, rToStr)
}

Finally, we let’s add a logout link. To do that, we just remove the key from a session, forcing users to authenticate with Github/Twitter again. We can do that using Session.Delete(..) function:

// actions/auth.go
func AuthDestroy(c buffalo.Context) error {
c.Session().Delete("key")
return c.Redirect(http.StatusFound, "/")
}

Finally, we can add our authentication middleware to the pages we want to have a authorized-only access:

// actions/app.go
...
auth := app.Group("/auth")
bah := buffalo.WrapHandlerFunc(gothic.BeginAuthHandler)
auth.GET("/{provider}", bah)
auth.GET("/{provider}/callback", AuthCallback)
auth.DELETE("", AuthDestroy)

app.Resource("/jobs", JobsResource{&buffalo.BaseResource{}}).Use(SetCurrentUser())

Okay fellas (according to slack) this is it. We’ve done it, We’ve added highly-not-production-ready authentication to our application! We can (we should) extend it with a proper validation, by storing them eg. in the database, adding button for signup/signin. Right now, we’re good to go.

“I do not enlighten those who are not eager to learn, nor arouse those who are not anxious to give an explanation themselves. If I have presented one corner of the square and they cannot come back to me with the other three, I should not go over the points again.” -Confucius

The full source code of this example is available on Github.

You can buy me coffee.

STAY SAFE

--

--

Connel Asikong

Programmer, Designer, Promoter, music lover, God fearer. thrilled about technology. wish I could help and do more.