Skip to content

Instantly share code, notes, and snippets.

@luxarts
Created October 13, 2023 06:58
Show Gist options
  • Save luxarts/b9d5175fd85b401f13e1f7ca2839216a to your computer and use it in GitHub Desktop.
Save luxarts/b9d5175fd85b401f13e1f7ca2839216a to your computer and use it in GitHub Desktop.
Google OAuth2 Example
package main
import (
"bufio"
"context"
"crypto/sha256"
"encoding/base64"
"fmt"
"github.com/google/uuid"
"github.com/joho/godotenv"
"golang.org/x/oauth2"
"golang.org/x/oauth2/google"
"google.golang.org/api/docs/v1"
"google.golang.org/api/drive/v2"
"google.golang.org/api/option"
"google.golang.org/api/sheets/v4"
"log"
"net/http"
"net/url"
"os"
"os/exec"
"strings"
)
const (
envAccessToken = "ACCESS_TOKEN"
envRefreshToken = "REFRESH_TOKEN"
googleCredentialsFile = "credentials.json"
tokenEnvFile = "token.env"
scopeDriveRW = "https://www.googleapis.com/auth/drive"
)
func main() {
scopes := []string{scopeDriveRW}
credentials := getCredentials(googleCredentialsFile)
oauthConfig, err := google.ConfigFromJSON(credentials, scopes...)
if err != nil {
log.Fatalf("Error creating Google Config:\n%+v\n", err)
}
err = godotenv.Load(tokenEnvFile)
if err != nil {
log.Fatalf("Error reading token.env:\n%+v\n", err)
}
if os.Getenv(envRefreshToken) == "" {
loginURL, codeVerifier := getLoginURL(oauthConfig)
openBrowser(loginURL)
code := getCodeFromCallback()
token := exchangeCodeWithToken(oauthConfig, code, codeVerifier)
saveToken(token)
os.Setenv(envAccessToken, token.AccessToken)
os.Setenv(envRefreshToken, token.RefreshToken)
}
ctx := context.Background()
googleClient := oauthConfig.Client(ctx, &oauth2.Token{
AccessToken: os.Getenv(envAccessToken),
RefreshToken: os.Getenv(envRefreshToken),
})
createSpreadsheet(googleClient)
docID := createDoc(googleClient)
readDoc(googleClient, docID)
getAllFiles(googleClient)
}
func getCredentials(fileName string) []byte {
credentials, err := os.ReadFile(fileName)
if err != nil {
log.Fatalf("Error reading credentials:\n%+v\n", err)
}
return credentials
}
func getLoginURL(c *oauth2.Config) (string, string) {
state := uuid.New().String()
codeVerifier := base64.RawURLEncoding.EncodeToString([]byte(uuid.New().String()))
codeVerifierHash := sha256.Sum256([]byte(codeVerifier))
codeChallenge := base64.RawURLEncoding.EncodeToString(codeVerifierHash[:])
authURL := c.AuthCodeURL(
state,
oauth2.AccessTypeOffline,
oauth2.SetAuthURLParam("code_challenge", codeChallenge),
oauth2.SetAuthURLParam("code_challenge_method", "S256"),
)
return authURL, codeVerifier
}
func openBrowser(url string) {
err := exec.Command("cmd", "/c", "start", strings.Replace(url, "&", "^&", -1)).Run()
if err != nil {
log.Fatalf("Error opening browser:\n%+v\n", err)
}
}
func getCodeFromCallback() string {
httpServerChan := make(chan bool, 1)
var authCode string
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
log.Println("Auth code recibido!")
u, err := url.Parse(r.RequestURI)
if err != nil {
log.Fatalln(err)
}
queryParams, err := url.ParseQuery(u.RawQuery)
if err != nil {
log.Fatalln(err)
}
authCode = queryParams.Get("code")
w.WriteHeader(http.StatusOK)
w.Write([]byte("Listo, ya podés cerrar esta ventana."))
httpServerChan <- true
})
server := &http.Server{Addr: ":80"}
go server.ListenAndServe()
log.Println("Esperando code en callback")
<-httpServerChan
_ = server.Shutdown(context.Background())
return authCode
}
func exchangeCodeWithToken(c *oauth2.Config, code string, codeVerifier string) *oauth2.Token {
log.Println("Intercambiando code por token")
ctx := context.Background()
token, err := c.Exchange(ctx, code, oauth2.SetAuthURLParam("code_verifier", codeVerifier))
if err != nil {
log.Fatalf("Error exchangig code:\n%+v\n", err)
}
return token
}
func saveToken(token *oauth2.Token) {
log.Println("Guardando token")
envFile, _ := os.OpenFile(tokenEnvFile, os.O_WRONLY|os.O_CREATE, 0644)
defer envFile.Close()
fmt.Fprintf(envFile, "ACCESS_TOKEN=%s\nREFRESH_TOKEN=%s", token.AccessToken, token.RefreshToken)
err := bufio.NewWriter(envFile).Flush()
if err != nil {
log.Fatalln(err)
}
envFile.Close()
}
func createSpreadsheet(googleClient *http.Client) {
sheetsSvc, err := sheets.NewService(context.Background(), option.WithHTTPClient(googleClient))
if err != nil {
log.Fatalln(err)
}
sprdsht, err := sheetsSvc.Spreadsheets.Create(
&sheets.Spreadsheet{
Properties: &sheets.SpreadsheetProperties{
Title: "test-oauth-spreadsheet",
},
}).Do()
if err != nil {
log.Fatalln(err)
}
log.Printf("Spreadsheet created with ID: %s\n", sprdsht.SpreadsheetId)
}
func createDoc(googleClient *http.Client) string {
docsSvc, err := docs.NewService(context.Background(), option.WithHTTPClient(googleClient))
if err != nil {
log.Fatalln(err)
}
doc, err := docsSvc.Documents.Create(
&docs.Document{
Title: "test-oauth-doc",
}).Do()
if err != nil {
log.Fatalln(err)
}
log.Printf("Doc created with ID: %s\n", doc.DocumentId)
return doc.DocumentId
}
func readDoc(googleClient *http.Client, docID string) {
docsSvc, err := docs.NewService(context.Background(), option.WithHTTPClient(googleClient))
if err != nil {
log.Fatalln(err)
}
doc, err := docsSvc.Documents.Get(docID).Do()
if err != nil {
log.Fatalln(err)
}
log.Printf("Doc title: %s\n", doc.Title)
log.Println("Doc body:")
for _, l := range doc.Body.Content {
if l.Paragraph == nil {
continue
}
log.Printf("%s", l.Paragraph.Elements[0].TextRun.Content)
}
}
func getAllFiles(googleClient *http.Client) {
driveSvc, err := drive.NewService(context.Background(), option.WithHTTPClient(googleClient))
if err != nil {
log.Fatalln(err)
}
filesList, err := driveSvc.Files.List().Do()
if err != nil {
log.Fatalln(err)
}
log.Println("Items:")
for _, item := range filesList.Items {
log.Printf(item.Title)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment