website logo

Last Updated:

A Complete Guide to Oauth2 in Golang

feature.webp

OAuth or open authorization is a standard protocol to authorize a 3rd party client to access resources on behalf of a user.

Let’s take an example to understand this statement. We all have a Gmail address. But Gmail doesn’t have a desktop client. Therefore, you need to use a 3rd party desktop mail client e.g., Thunderbird or Mailspring. But the problem is, how a 3rd party email client can access your Gmail inbox?

To solve this problem, email clients use OAuth. When you log in using OAuth, you give authorization to the email client to access your Gmail on behalf of you.

You want another example to get a good understanding? Ok let’s take an example of automated website deployment in Netlify. You can link your GitHub or Gitlab repository and deploy your website very easily on Netlify.

To perform a deployment, first these platforms should pull your repository from GitHub. But they can’t do that unless you authorize them to do so. You can give them this authorization using GitHub OAuth and make a successful deployment.

Why developer use OAuth to authenticate a user

Google, GitHub, GitLab etc are called OAuth providers. There are numerous OAuth providers present, but these are the most popular among developer community.

When a user creates an account on their website, they collect basic user information like name, email, profile picture etc. OAuth providers provide The user information from an API endpoint.

Now as a developer, you build applications. You want basic user information to authenticate and create user accounts. OAuth gives you certain advantages over traditional email-password authentication.

  1. You don’t need to store passwords.
  2. User email, name, and images are readily available. Making account creation process smooth and easy.
  3. Don’t need to filter spam and bots. OAuth provider takes care of it.

After all this explanation, You may understand the reason to use OAuth to authenticate a user. Now let’s understand the process by which OAuth works.

Understand the OAuth flow

Let’s again take an example to understand the OAuth flow. Here I am focusing on server-side OAuth implementation.

Every OAuth provider issue a client ID and client secret to use their OAuth service. Make sure to get a pair of these credentials.

Assume you have 3 routes defined in your server, /login, /callback and /dashboard.

  1. /login: This route takes a login request and generates a unique OAuth URL using client ID and Client secret. Then redirect the user to the generated URL. The generated URL lands on the OAuth provider website.
  2. /callback: Once client completes the authentication process on the OAuth provider website, he/she redirect back to the original website to a predefined route. This route process the redirected callback from the OAuth provider.
  3. /dashboard: This is a user dashboard. A protected route. When the /callback route successfully create a user account, user is redirected to this route.

We can divide the OAuth flow into 9 steps.

  1. User comes to the /login route.
  2. In the /login route, server generate a unique OAuth URL and redirect the user to that URL.
  3. User Follow this unique URL and land on the OAuth provider website.
  4. User give their credentials and login on the provider’s website.
  5. On successful login, user redirected to the /callback URL.
  6. The redirect URL contain a code in the query parameter.
  7. Inside the /callback route, the code parameter is extracted from the query parameter.
  8. Server exchange this code to get an Access Token from the OAuth provider.
  9. Server call OAuth provider API with the Access Token to get the user information.
  10. Server use this information to create a user account.

There are some additional information you should keep in your mind.

  1. There are 2 access types in the OAuth protocol. If the OAuth access type is set to offline, a Refresh Token is generated in addition to an Access Token. This Refresh Token can be used to regenerate the Access Token without re-authenticating the user.
  2. While generating the unique OAuth URL in the /login route, a state variable can be passed in the request URL. The OAuth provider return the same state variable in /callback route. You can use this feature to restore the client state or mitigate cross site request forgery (CSRF) attack.

OAuth2 in Golang

Golang has an official OAuth2 implementation in golang.org/x/oauth2 package. There are also multiple OAuth2 provider specific packages like golang.org/x/oauth2/google, golang.org/x/oauth2/facebook, golang.org/x/oauth2/github etc.

A complete example for this blog is present in my GitHub profile. Do check this out.

Creating the OAuth2 configuration

To use this OAuth2 package, first you need to create an oAuth2.config.

// Code copied from : https://pkg.go.dev/golang.org/x/oauth2#Config

type Config struct {
	// ClientID is the application's ID.
	ClientID [string] //(https://pkg.go.dev/builtin#string)

	// ClientSecret is the application's secret.
	ClientSecret [string] //(https://pkg.go.dev/builtin#string)

	// Endpoint contains the resource server's token endpoint
	// URLs. These are constants specific to each server and are
	// often available via site-specific packages, such as
	// google.Endpoint or github.Endpoint.
	Endpoint [Endpoint] //(https://pkg.go.dev/golang.org/x/oauth2#Endpoint)

	// RedirectURL is the URL to redirect users going through
	// the OAuth flow, after the resource owner's URLs.
	RedirectURL [string] //(https://pkg.go.dev/builtin#string)

	// Scope specifies optional requested permissions.
	Scopes [][string] //(https://pkg.go.dev/builtin#string)
	// contains filtered or unexported fields
}

This is the definition of OAUth2 config. Let’s understand the parameters in the Config struct.

You can get the ClientID and ClientSecret from the OAuth provider.

OAuth provider specific packages contain the Endpoint. For example, to use Google endpoint, use golang.org/x/oauth2/google package.

When a user completes their authentication, they are redirected to the RedirectedURL. Make sure to register this URL in the OAuth provider website.

Scope contain the scope of data you want to access from the OAuth provider on behalf of the user. In most cases, you just need the user email and basic information. For that, use ["email","user"] as scope.

This is an example of the OAuth2 config.

// access them from environment variable
clientid := os.Getenv("CLIENT_ID")
clientSecret := os.Getenv("CLIENT_SEC")

// Oauth config manage the OAuth flow. You have to register
// the redirect url in the OAuth provider. For the endpoint,
// there are many provider specific package inside the
// golang.org/x/oauth2 package
conf := &oauth2.Config{
		ClientID:     clientid,
		ClientSecret: clientSecret,
		RedirectURL:  "http://localhost:8000/auth/callback",
		Scopes:       []string{"email", "profile"},
		Endpoint:     google.Endpoint,
}

Generating OAUth2 URL

In the previous step you have created an instance of oauth2.config. We can use the AuthCodeURL method to generate an OAuth URL.

url := conf.AuthCodeURL("some-user-state", oauth2.AccessTypeOffline)

The AuthCodeURL method accepts 2 parameters. First string parameter is called state. You can use this state to restore the user session. State is also used to mitigate CSRF attack.

The second parameter is used to define the access type. This access type may be online of offline.

  1. Online access type: on successful authentication, it gives an access token. You can use this access token to retrieve user information from the OAuth provider.

  2. Offline access type: On successful authentication, it gives a refresh token in addition to an access token. Refresh token can be used to regenerate the access token.

Then you redirect the user to this generated OAuth2 URL.

http.Redirect(w, r, url, http.StatusTemporaryRedirect)

Process OAuth2 response code

When a user completed the authentication process on the OAuth2 provider’s website, it redirects the user to the redirect url. You have to provide the redirect url beforehand, during Oauth2 client registration.

The redirect URL contain 2 query parameter, code and state.

  1. code: This query parameter contains the access code. Access code is required to get the access token.
  2. state: The OAuth provider echoes the state value set during the OAuth URL generation step. You can use state parameter to verify previously set state and restore the user state.

Retrieving the access token from code is a 2-step process.

  1. Get the code from query parameter.
code := r.URL.Query().Get("code")
  1. Exchange the code for an access token. The Exchange method communicate with the Oauth provider and fetch the access token.
t, err := conf.Exchange(context.Background(), code)
if err != nil {
	http.Error(w, err.Error(), http.StatusBadRequest)
	return
}

Here, t is a struct that contain the access token and refresh token.

Retrieve user information

The access token is used to communicate with the Oauth provider, on behalf of the user. Follow these steps to retrive user email and public information.

  1. Make an HTTP client from the Oauth config. If your access token expires, this method automatically generates the access token from refresh token.
client := conf.Client(context.Background(), t)
  1. Make a GET request to the user endpoint to retrieve user information. The URL is different for different provider.
resp, err := client.Get("https://www.googleapis.com/oauth2/v2/userinfo")
if err != nil {
	http.Error(w, err.Error(), http.StatusBadRequest)
	return
}
  1. Read the response body. The response is a JSON encoded value.
defer resp.Body.Close()

var v any

// Reading the JSON body using JSON decoder
err = json.NewDecoder(resp.Body).Decode(&v)
if err != nil {
	http.Error(w, err.Error(), http.StatusInternalServerError)
	return
}

// printing the json data into std out.
fmt.printf("%v", v)

Google login in Golang

The golang.org/x/oauth2/google package contain the Google oauth related configurations and methods.

Please read the OAuth in Golang section before following these steps.

  1. Install the Google oauth2 package.
go get golang.org/x/oauth2/google
  1. Get the client ID and client secret from the Google cloud credentials dashboard.
  2. Add google.endpoint in the OAuth2 configuration.
conf := &oauth2.Config{
	ClientID:     clientid,
	ClientSecret: clientSecret,
	RedirectURL:  "http://localhost:8000/auth/callback",
	Scopes:       []string{"email", "profile"},
	Endpoint:     google.Endpoint,
}
  1. Make a /auth/login/google route to generate Google OAuth URL and redirect the user to the Google login page.
mux.HandleFunc("GET /auth/login/google", googleHandler)

func googleHandler(w http.ResponseWriter, r *http.Request) {
	url := conf.AuthCodeURL("state", oauth2.AccessTypeOffline)
	http.Redirect(w, r, url, http.StatusTemporaryRedirect)
}
  1. Make a /auth/callback/google route to handle callback from Google OAuth and get the code.
mux.HandleFunc("GET /auth/callback/google", googleCallbackHandler)

func googleCallbackHandler(w http.ResponseWriter, r *http.Request) {
	code := r.URL.Query().Get("code")
}
  1. In the same handler, exchange this code for an access token.
func googleCallbackHandler(w http.ResponseWriter, r *http.Request) {
	code := r.URL.Query().Get("code")

	t, err := conf.Exchange(context.Background(), code)
	if err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}
}
  1. Use this access token to make an HTTP client. This method auto-generate the access token if it is expired.
func googleCallbackHandler(w http.ResponseWriter, r *http.Request) {
	code := r.URL.Query().Get("code")

	t, err := conf.Exchange(context.Background(), code)
	...

	client := conf.Client(context.Background(), t)
}
  1. Use the HTTP client to fetch user information from Google.
func googleCallbackHandler(w http.ResponseWriter, r *http.Request) {
	code := r.URL.Query().Get("code")

	t, err := conf.Exchange(context.Background(), code)
	...

	client := conf.Client(context.Background(), t)

	resp, err := client.Get("https://www.googleapis.com/oauth2/v2/userinfo")
	if err != nil {
			http.Error(w, err.Error(), http.StatusBadRequest)
			return
	}
}
  1. Read the response using a JSON decoder.
func googleCallbackHandler(w http.ResponseWriter, r *http.Request) {
	code := r.URL.Query().Get("code")

	t, err := conf.Exchange(context.Background(), code)
	...

	client := conf.Client(context.Background(), t)

	resp, err := client.Get("https://www.googleapis.com/oauth2/v2/userinfo")
	...

	defer resp.Body.Close()

    var v any

	// Reading the JSON body using JSON decoder
	err = json.NewDecoder(resp.Body).Decode(&v)
	if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
	}
}
  1. Persist the user data and redirect the user to a protected page.
func googleCallbackHandler(w http.ResponseWriter, r *http.Request) {
	code := r.URL.Query().Get("code")

	t, err := conf.Exchange(context.Background(), code)
	...

	client := conf.Client(context.Background(), t)

	resp, err := client.Get("https://www.googleapis.com/oauth2/v2/userinfo")
	...

	defer resp.Body.Close()

    var v any

	// Reading the JSON body using JSON decoder
	err = json.NewDecoder(resp.Body).Decode(&v)
	...

    http.Redirect(w, r, "/dashboard", http.StatusTemporaryRedirect)
}

GitHub login in Golang

GitHub OAuth login is very similar to Google login. But you have to make some changes. I am listing the changes in the following steps.

  1. Get the GitHub OAuth2 package
go get golang.org/x/oauth2/github
  1. Set Oauth2 configuration endpoint to GitHub endpoint.
conf := &oauth2.Config{
	...
	Endpoint:     github.Endpoint,
}
  1. Change the API URL to get user details from GitHub.
resp, err := client.Get("https://api.github.com/user")

Manage User session after authentication

After a user is successfully authenticated by a OAuth provider, a user session should be maintained. There are many options to manage a user session, e.g, Gorilla session, SCS etc.

I personally like SCS due to its simplicity. Let’s see how to use the scs package to manage user sessions.

  1. Get the scs package.
go get github.com/alexedwards/scs/v2
  1. Initialize the session manager. By default, session is valid for 24 hours.
var sessionManager *scs.SessionManager

func main(){
	...

	sessionManager = scs.New()
	...
}
  1. Add SCS middleware to handle all the heavy-lifting for you.
http.ListenAndServe(":4000", sessionManager.LoadAndSave(mux))
  1. Inside the googleCallbackHandler, set the email of the user as session data.
func googleCallbackHandler(w http.ResponseWriter, r *http.Request) {
	...
	err = json.NewDecoder(resp.Body).Decode(&v)
	sessionManager.Put(r.Context(), "email", v.Email)
	...
}
  1. To read the session data inside a handler, use the Get method.
email := sessionManager.GetString(r.Context(), "email")

Conclusion

In this blog, you have learned how OAuth flow actually works. Then I have explained the OAuth2 library in Golang. The library is implimented very nicely and very easy to use. Finally, I gave you example for Google OAuth login and GitHub OAuth Login in Golang code examples. In addition, you have also learned the use of a session manager to manage the auth state of the user.

Hope you like this blog. Subscribe for my newsletter to get notification about my latest blogs and some of my very intersting findings.

Some good resources I stumbled upon

See Also