« Back to Index

OAuth

View original Gist on GitHub

OAuth.md

OAuth is a mechanism that allows a user to authorize your application to access his/her data from another service without giving you their authentication details.

For a banking monitoring application (e.g. your application reads the user’s banking data and displays it within some useful diagrams), the steps could look something like the following:

  1. User requests API action from your application (e.g. show me my spending graph)
  2. Your application requests access to your bank
  3. User’s bank service tells the users your application would like access to it
  4. User accepts or declines the request to access your banking data
  5. The bank provides a temporary code to your application
  6. Your app requests a token while passing back the code it was given from the bank
  7. Banking service verifies the code is a match/valid and provides a token
  8. Your app can now request data from the bank using the provided token

GitHub API

Steps for Application

Steps for User

Notes

If you restart your application (see below for code), and the user goes back to the /login page, then clicking on the login button will attempt to access the token and realise it already exists and so redirects back to the login page again. Check the console/terminal output for user details that have been printed.

The user should now be able to see your application listed in their authorized applications: https://github.com/settings/applications

Application Code

https://blog.kowalczyk.info/article/f/Accessing-GitHub-API-from-Go.html

package main

import (
	"fmt"
	"net/http"

	"github.com/google/go-github/github"
	"golang.org/x/oauth2"
	githuboauth "golang.org/x/oauth2/github"
)

var (
	// You must register the app at https://github.com/settings/applications
	// Set callback to http://127.0.0.1:5000/github_oauth_cb
	// Set ClientId and ClientSecret values taken from GitHub registration page
	// Note: revoked the already details
	oauthConf = &oauth2.Config{
		ClientID:     "x",
		ClientSecret: "x",
		Scopes:       []string{"user:email", "repo"},
		Endpoint:     githuboauth.Endpoint,
	}
	// random string for oauth2 API calls to protect against CSRF
	oauthStateString = "thisshouldberandom"
)

const htmlIndex = `<html><body>
Login with <a href="/login">GitHub</a>
</body></html>
`

// /
func handleMain(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "text/html; charset=utf-8")
	w.WriteHeader(http.StatusOK)
	w.Write([]byte(htmlIndex))
}

// /login redirects to GitHub’s authorization page:
// GitHub will show authorization page to your user
// If the user authorizes your app, GitHub will re-direct to OAuth callback
func handleGitHubLogin(w http.ResponseWriter, r *http.Request) {
	url := oauthConf.AuthCodeURL(oauthStateString, oauth2.AccessTypeOnline)
	http.Redirect(w, r, url, http.StatusTemporaryRedirect)
}

// /github_oauth_cb. Called by GitHub after authorization is granted
// This handler accesses the GitHub token, and converts the token into a http client
// We then use that http client to list GitHub information about the user
func handleGitHubCallback(w http.ResponseWriter, r *http.Request) {
	state := r.FormValue("state")
	if state != oauthStateString {
		fmt.Printf("invalid oauth state, expected '%s', got '%s'\n", oauthStateString, state)
		http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
		return
	}

	code := r.FormValue("code")
	token, err := oauthConf.Exchange(oauth2.NoContext, code)
	if err != nil {
		fmt.Printf("oauthConf.Exchange() failed with '%s'\n", err)
		http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
		return
	}

	oauthClient := oauthConf.Client(oauth2.NoContext, token)
	client := github.NewClient(oauthClient)
	user, _, err := client.Users.Get("")
	if err != nil {
		fmt.Printf("client.Users.Get() faled with '%s'\n", err)
		http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
		return
	}
	fmt.Printf("Logged in as GitHub user: %s\n", *user.Login)
	http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
}

func main() {
	http.HandleFunc("/", handleMain)
	http.HandleFunc("/login", handleGitHubLogin)
	http.HandleFunc("/github_oauth_cb", handleGitHubCallback)
	fmt.Print("Started running on http://127.0.0.1:5000\n")
	fmt.Println(http.ListenAndServe(":5000", nil))
}

Handling State

If you want your user state to be persistent, then you would need to store it in a permanent/persistent store; there are lots of options for that (e.g. a distributed redis cluster - so the data is available across machines and allows your web servers to scale horizontally)

This also means you’ll need to handle your own authentication and / or authorization in your web app to use that persistent data in order to make those upstream requests on behalf of a specific user.

You could also keep a cookie in the browser (encrypted and hashed) with the token and some basic user info. But I’m not overly confident with using cookies.