Last active
January 21, 2018 17:08
-
-
Save srishanbhattarai/d0f24b968c41dec08b1f4e0ddc0a5480 to your computer and use it in GitHub Desktop.
Proof of concept OAuth flow in RESTful Go servers
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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