Skip to content

Instantly share code, notes, and snippets.

@srishanbhattarai
Last active January 21, 2018 17:08
Show Gist options
  • Save srishanbhattarai/d0f24b968c41dec08b1f4e0ddc0a5480 to your computer and use it in GitHub Desktop.
Save srishanbhattarai/d0f24b968c41dec08b1f4e0ddc0a5480 to your computer and use it in GitHub Desktop.
Proof of concept OAuth flow in RESTful Go servers
package main
import (
"encoding/json"
"fmt"
"io/ioutil"
"log"
"net/http"
"os"
"github.com/gorilla/mux"
"github.com/joho/godotenv"
)
type URL string
type GithubAuthResponse struct {
AccessToken string `json:"access_token"`
TokenType string `json:"token_type"`
Scopes string `json:"scopes"`
}
// URL to exchange callback code for an oauth token
const GITHUB_USER_LOGIN_URL = "https://github.com/login/oauth/authorize?scope=user:email&client_id=%s"
const GITHUB_POST_CALLBACK_URL URL = "https://github.com/login/oauth/access_token"
func main() {
err := loadEnv()
if err != nil {
log.Panic("Could not load .env")
}
r := mux.NewRouter()
r.HandleFunc("/oauth/callback", githubOauthCallback).Methods("GET")
r.HandleFunc("/app-config", appConfig).Methods("GET")
log.Panic(http.ListenAndServe(":9000", r))
}
// Github Oauth Callback URL.
// Use this to
// 1 - exchange the code sent in the callback for an access token
// 2 - request github to provider whatever info you need using that token
// 3- bring your database up to date.
// 4 - redirect to UI
func githubOauthCallback(w http.ResponseWriter, r *http.Request) {
code := r.URL.Query().Get("code")
httpClient := &http.Client{}
url := GITHUB_POST_CALLBACK_URL.
addQueryParam("client_id", os.Getenv("GITHUB_OAUTH_CLIENT_ID"), true).
addQueryParam("client_secret", os.Getenv("GITHUB_OAUTH_CLIENT_SECRET"), false).
addQueryParam("code", code, false).
addQueryParam("client_id", os.Getenv("GITHUB_OAUTH_CLIENT_ID"), false)
req, err := http.NewRequest("POST", string(url), nil)
if err != nil {
log.Panicf("Could not compose POST req: %s", err.Error())
}
req.Header.Add("accept", "application/json")
fmt.Printf("Request URL: %s\n", req.URL)
resp, err := httpClient.Do(req)
if err != nil {
// coudlnt make post req
}
if resp.StatusCode != http.StatusOK {
// unsuccessful
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
githubResponse := &GithubAuthResponse{}
json.Unmarshal(body, &githubResponse)
// do db tasks with the access token.
// for jwt based systems, create a new session with a new jwt based on the user's email (which is the identifying factor here.)
// github's token wont go to the client but the jwt session that we create will.
fmt.Printf("JSON: Body = %v, Code = %d\n", githubResponse, resp.StatusCode)
// This should be the web-apps callback route.
http.Redirect(w, r, "http://localhost:3000", http.StatusTemporaryRedirect)
}
// Handler for "/app-config" route.
// Web app calls this to get the OAuth URL.
func appConfig(w http.ResponseWriter, r *http.Request) {
type GithubOAuth struct {
ClientId string `json:"clientId"`
LoginURL string `json:"loginURL"`
}
clientId := os.Getenv("GITHUB_OAUTH_CLIENT_ID")
github := GithubOAuth{
ClientId: clientId,
LoginURL: fmt.Sprintf(GITHUB_USER_LOGIN_URL, clientId),
}
json.NewEncoder(w).Encode(github)
}
// Load the local .env file.
func loadEnv() error {
err := godotenv.Load()
return err
}
func (url URL) addQueryParam(key, value string, isFirst bool) URL {
var separator string
if isFirst {
separator = "?"
} else {
separator = "&"
}
qp := separator + key + "=" + value
return url + URL(qp)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment