惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

GbyAI
GbyAI
酷 壳 – CoolShell
酷 壳 – CoolShell
宝玉的分享
宝玉的分享
IT之家
IT之家
Recent Announcements
Recent Announcements
T
The Blog of Author Tim Ferriss
雷峰网
雷峰网
阮一峰的网络日志
阮一峰的网络日志
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
M
MIT News - Artificial intelligence
D
Docker
C
CERT Recently Published Vulnerability Notes
月光博客
月光博客
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Recorded Future
Recorded Future
博客园 - 司徒正美
D
DataBreaches.Net
Last Week in AI
Last Week in AI
U
Unit 42
人人都是产品经理
人人都是产品经理
博客园_首页
Blog — PlanetScale
Blog — PlanetScale
量子位
大猫的无限游戏
大猫的无限游戏
博客园 - Franky
T
Tailwind CSS Blog
小众软件
小众软件
Y
Y Combinator Blog
WordPress大学
WordPress大学
B
Blog RSS Feed
C
Check Point Blog
H
Help Net Security
The Last Watchdog
The Last Watchdog
F
Full Disclosure
腾讯CDC
V
Visual Studio Blog
Google Online Security Blog
Google Online Security Blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
T
Troy Hunt's Blog
N
News and Events Feed by Topic
F
Fortinet All Blogs
B
Blog
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
J
Java Code Geeks
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
有赞技术团队
有赞技术团队
博客园 - 三生石上(FineUI控件)
TaoSecurity Blog
TaoSecurity Blog
I
InfoQ
V
Vulnerabilities – Threatpost

Dizzy zone

Pangolin Private Resources With Domain Https About Redis is fast - I'll cache in Postgres n8n and large files Malicious Node install script on Google search Wrapping Go errors with caller info BLAKE2b performance on Apple Silicon State of my Homelab 2025 My homelabs power consumption On Umami ML for related posts on Hugo Probabilistic Early Expiration in Go SQLC & dynamic queries Enums in Go Streaming Netdata metrics from TrueNAS SCALE SQL string constant gotcha Moving from Jenkins to Drone My new server: MSI Cubi 3 Silent My thoughts on Ansible® Profiling gin with pprof How I host this blog, CI and tooling Refactoring Go switch statements I made my own commenting server. Here's why. Why I hate OpenApi(swagger) IDE for GO Jenkins on raspberry pi 3 How I started my professional career Kestrel vs Gin vs Iris vs Express vs Fasthttp on EC2 nano Go's defer statement Self-hosted disqus alternative for 5$ a month Why I like go Speeding hexo (or any page) for PageSpeed insights Starting a blog with hexo and AWS S3
OAuth with Gin and Goth
Vik · 2018-06-01 · via Dizzy zone

OAuth logo.

When I created mouthful, I was intending it to be rather light and not feature rich but after getting a few feature requests getting in, I’ve decided to expand it. One of the issues was a request to reuse logon credentials for the admin panel. For that, I’ve needed OAuth. I did not have much prior experience with OAuth, so it did intimidate me a bit. However, after implementing OAuth for mouthful, I can say that nowadays - it’s rather easy including OAuth in your applications as well. It’s also a rather good idea to do so as people behind the providers such as github or facebook are probably going to do a better job than a lone developer like me will at securing your credentials. Anyway, with this post I’d like to show how easy it is to add OAuth to your gin project.

The start

Let’s start with a basic gin app, straight from the gin examples. One thing I’ll change is the default route. Instead of the default ping in the demo, we’ll serve some html.

package main

import (
	"net/http"
	"github.com/gin-gonic/gin"
)

func main() {
    r := gin.Default()
	htmlFormat := `<html><body>%v</body></html>`
	html := fmt.Sprintf(htmlFormat, "Test")
	r.GET("/", func(c *gin.Context) {
		c.Data(http.StatusOK, "text/html; charset=utf-8", []byte(html))
	})
	r.Run() // listen and serve on 0.0.0.0:8080
}

Understanding OAuth with goth

Now the pure magic part is the ability to use ready to use libraries for OAuth such as goth. Goth supports a ton of providers, so it means we can have all of them available for us, on our application. For the demo purposes, I’ll stick to a single one though. I’ll use the github provider. Using goth is rather easy. But before we start with that a quick refresher on OAuth. I’ll keep it a bit simplistic. Before OAuth can take place, you need a secret that you and the third party knows. This can be found on the providers webpage. Once you have those, the flow is basically as follows:

  • A user initiates the action to log in through a third party provider
  • The user is redirected to the 3rd parties provider to agree to giving OAuth access.
  • The user agrees and gives you OAuth access.
  • The provider then redirects the user back to your website with an auth code.
  • With the OAuth code your web server can then gain access and fetch users information.

With that, what we need to enable OAuth in this example is:

  1. A button to initiate the flow
  2. An auth endpoint to that the button will take the user to
  3. A callback that will get called once the auth is done, so we can gain the user info

Let’s start auth endpoint. For this, we’ll set up goth and let it handle all the complexity of OAuth. Here’s how to do it for github.

package main

import (
	"fmt"
	"net/http"
	"os"
	"github.com/markbates/goth"
	"github.com/markbates/goth/gothic"
	"github.com/markbates/goth/providers/github"
	"github.com/gin-gonic/gin"
)

func main() {
	r := gin.Default()
	githubProvider := github.New(os.Getenv("GITHUB_KEY"), os.Getenv("GITHUB_SECRET"), "http://localhost:8080/callback")
	goth.UseProviders(githubProvider)
	htmlFormat := `<html><body>%v</body></html>`
	r.GET("/", func(c *gin.Context) {
		html := fmt.Sprintf(htmlFormat, "Test")
		c.Data(http.StatusOK, "text/html; charset=utf-8", []byte(html))
	})
	r.Run() // listen and serve on 0.0.0.0:8080
}

We will need a github key and a github secret and those can be gotten under developer settings of your account. We also provide a callback address, and that will be used for registering an OAuth application under your github account as well, so do make sure to use the one I’ve added if you’re following along with this.

Now all we need to implement a couple of endpoints, the auth redirect and the callback.

package main

import (
	"encoding/json"
	"fmt"
	"net/http"
	"os"

	"github.com/gin-gonic/gin"
	"github.com/markbates/goth"
	"github.com/markbates/goth/gothic"
	"github.com/markbates/goth/providers/github"
)

func main() {
	r := gin.Default()
	githubProvider := github.New(os.Getenv("GITHUB_KEY"), os.Getenv("GITHUB_SECRET"), "http://localhost:8080/callback")
	goth.UseProviders(githubProvider)
	htmlFormat := `<html><body>%v</body></html>`
	r.GET("/", func(c *gin.Context) {
		html := fmt.Sprintf(htmlFormat, "Test")
		c.Data(http.StatusOK, "text/html; charset=utf-8", []byte(html))
	})
	r.GET("/github", func(c *gin.Context) {
		q := c.Request.URL.Query()
		q.Add("provider", "github")
		c.Request.URL.RawQuery = q.Encode()
		gothic.BeginAuthHandler(c.Writer, c.Request)
	})
	r.GET("/callback", func(c *gin.Context) {
		q := c.Request.URL.Query()
		q.Add("provider", "github")
		c.Request.URL.RawQuery = q.Encode()
		user, err := gothic.CompleteUserAuth(c.Writer, c.Request)
		if err != nil {
			c.AbortWithError(http.StatusInternalServerError, err)
			return
		}
		res, err := json.Marshal(user)
		if err != nil {
			c.AbortWithError(http.StatusInternalServerError, err)
			return
		}
		jsonString := string(res)
		html := fmt.Sprintf(htmlFormat, jsonString)
		c.Data(http.StatusOK, "text/html; charset=utf-8", []byte(html))
	})
	r.Run() // listen and serve on 0.0.0.0:8080
}

The /github endpoint will do the redirect to github. You might be wondering why do I need to manipulate the request by adding the value provider to the query. Well, in all reality - you don’t if you’re using proper rest API practices and working with multiple OAuth providers. Goth uses the query to figure out which of the registered OAuth providers to use to try and initiate the flow. I’m just faking the query via the three lines:

q := c.Request.URL.Query()
q.Add("provider", "github")
c.Request.URL.RawQuery = q.Encode()

I’m also doing the same in the callback, so - ignore them. In a proper, non demo solution this is not needed as you’ll have the provider passed in as a parameter in a route. The meaty parts here are the gothic.BeginAuthHandler(c.Writer, c.Request) that’s responsible for beginning the OAuth flow and user, err := gothic.CompleteUserAuth(c.Writer, c.Request) for completing the flow and parsing the user details. I’ve also added the user details as a serialized json to our output html once the flow is complete via the callback.

Now onto the button. All we need to do is redirect the user to the /github endpoint. I simply change the routers GET / to the following:

r.GET("/", func(c *gin.Context) {
	html := fmt.Sprintf(htmlFormat, `<a href="/github">Login through github</a>`)
	c.Data(http.StatusOK, "text/html; charset=utf-8", []byte(html))
})

With that, our OAuth setup is complete. Run your go application, just be sure to set the environment variables GITHUB_KEY and GITHUB_SECRET before hand. You should now be able to log in and see all the details in json format that github provides for your user.

Why do you want this?

In general, handling user credentials is a great responsibility and one should never take it lightly. Therefore it is best to use OAuth if at all possible and not bother making your own Auth services. This allows for greater safety, as it is rather unlikely you’ll come up with a solution more secure than some of the big players. It is rarely a good idea to implement your own Auth solution! With tools like Goth that make this process trivial - there is no excuse for making one yourself.

The full code snippet is also available as a gist.

Had any issues following the guide? Have I gotten anything wrong? Do let me know in the comments below.