ເຮົາມາລອງສ້າງ OAuth Facebook ໃນ go ແບບງ່າຍໆ
Tags: go, facebook, echo, oauth
Published: 2023-07-28 07:16:43

> ບົດຄວາມນີ້ເຮົາຈະມາລອງສ້າງລະບົບ OAuth Facebook, ສັງເກດເວລາທີ່ເຮົາຈະເຂົ້າໃຊ້ລະບົບລະໃດໜຶ່ງຈະເຫັນວ່າມີປຸ່ມໃຫ້ກົດ Login with Facebook, Login with Google...ຈະເຫັນວ່າມີປຸ່ມໃຫ້ກົດປະມານນີ້, ນັ້ນແຫຼະຄື **OAuth** ນັ້ນເອງ.

> OAuth ຫຼື Open Authentication ເປັນມາດຕະຖານການຢືນຢັນຕົວຕົນ ແລະ ການກວດສອບສິດທິບຸກຄົນອີກຮູບແບບໜຶ່ງໂດຍຕ້ອງໃຫ້ຜູ້ໃຊ້ທຳການຍອມຮັບບາງຢ່າງໃຫ້ Application ເພື່ອເຂົ້າເຖິງຂໍ້ມູນບາງສ່ວນຂອງບຸກຄົນ.

ຂັ້ນຕອນ: ກ່ອນອື່ນໃຫ້ເຮົາໄປທີ່ => https://developers.facebook.com/ ທຳການສະໝັກສະມາຊິກໃຫ້ຮຽບຮ້ອຍຫຼັງຈາກນັ້ນກໍໄປສ້າງແອັບຕາມຂັ້ນຕອນດັ່ງນີ້

1/ ກົດທີ່ MyApps ແລ້ວກົດ CreateApp ດ້ານຂ້ວາມືໃຫ້ເລືອກປະເພດແອັບເປັນ None

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rxcvcy9rkps76eess0m2.png)

2/ ຕັ້ງຊື່ແອັບ ແລະ ໃສ່ອີເມວໃຫ້ຮຽບຮ້ອຍກົດສ້າງໄດ້ເລີຍ

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/y0wkav7skpy4vc3tex2c.png)

3/ ຫຼັງຈາກທີ່ເຮົາສ້າງແອັບສຳເລັດແລ້ວເຮົາຈະໄດ້ key: App ID, App secret ເຮົາຈະເອົາໄປໃສ່ຕອນທີ່ເຮົາສ້າງ API ເປັນ config parameter

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zlj7oxdzcopzq6nyqmu8.png)

4/ ໄປຕັ້ງຄ່າ Platform ໃນທີ່ນີ້ເຮົາຈະທົດລອງຢູ່ Web browser ໃຫ້ໄປທີ່ Settings->Basic ເລືອນລົງມາທາງລຸ່ມແລ້ວກົດ Add platform ເລືອກ Website ກົດ Next

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dp0hchlu5n4pc5f3b6fw.png)

5/ ກຳນົດ Site URL ໃນທີ່ນີ້ເຮົາຈະ dev ຢູ່ local ເຮົາເອງກຳນົດເປັນ http://localhost:8080

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/v3b5gdjtds6ybxafcl4u.png)

6/ ເມື່ອກຳນົດ Site URL ແລ້ວຖ້າຈະໃສ່ Callback URI ໃຫ້ໃສ່ຢູ່ທີ່ຊ່ອງ Valid OAuth Redirect URIs ຕົວຢ່າງ: http://localhost:8080/callback

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qtzvih4vlixwonze6wh3.png)

> OAuth Facebook Flow

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hfpwedbxfen99t88adtj.png)

ຊິອະທິບາຍການເຮັດວຽກຂອງມັນຄາວໆຄື:
1/ ເຮົາຕ້ອງສ້າງ Service ໂຕໜຶ່ງຂຶ້ນມາເມື່ອມີການກົດຍິງເຂົ້າມາ Service ຕົວນີ້ປຶບມັນຈະທຳການ redirect ໄປຫາ https://www.facebook.com/v17.0/dialog/oauth
2/ ເມື່ອເຮົາທຳການ Authorize App Facebook ສຳເລັດແລ້ວ Facebook ຈະທຳການ redirect ກັບມາຫາເຮົາຕາມ Url Callback ທີ່ເຮົາໄດ້ກຳນົດໄວ້, ຕາມດ້ວຍ Query Params code ສົ່ງ key token ກັບມາຫາເຮົາ
3/ ຫລັງຈາກນັ້ນເຮົາຈະເອົາ key token ທີ່ໄດ້ມາຈາກ Facebook ເຮົາກໍນຳເອົາ Implement ລຳດັບທັດໄປຖ້າຕ້ອງການໄປດຶ່ງເອົາຂໍ້ມູນພື້ນຖານຈາກ facebook ເຊັ່ນ: ລະຫັດ,ຊື່,ອີເມວ...ໂດຍທີ່ເຮົາສົ່ງ key token ເພື່ອໄປຂໍທີ່ https://graph.facebook.com/v17.0/oauth/access_token

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cmcxx52dnzej5duntthf.png)

> ເອົາລ່ະມາຮອດຂັ້ນຕອນເຮົາ Codeding ກັນເລີຍ

1/ ສ້າງ Service Login ຕົວຢ່າງໃນ file main.go

```
package main

import (
	"context"
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"net/url"

	"github.com/labstack/echo/v4/middleware"

	"github.com/labstack/echo/v4"
)

const (
	ClientID     = "YOUR_APPID"
	ClientSecret = "YOUR_APP_SECRET"
	RedirectURL  = "YOUR_CALLBACK_URL"
)

const htmlIndex = `<html><body> Logged in with <a href="/login">facebook</a></body></html>`

func handleMain(c echo.Context) error {
	return c.HTML(http.StatusOK, htmlIndex)
}

func handleLogin(c echo.Context) error {
	authURL := "https://www.facebook.com/v17.0/dialog/oauth"
	params := url.Values{}
	params.Set("client_id", ClientID)
	params.Set("redirect_uri", RedirectURL)
	params.Set("response_type", "code")
	params.Set("scope", "email")

	redirectURL := authURL + "?" + params.Encode()
	return c.Redirect(http.StatusTemporaryRedirect, redirectURL)
}

func main() {
	e := echo.New()

	e.Use(
		middleware.RequestID(),
		middleware.Logger(),
		middleware.Recover(),
	)

	e.GET("/", handleMain)
	e.GET("/login", handleLogin)

	fmt.Println("Started running on http://localhost:8080")
	log.Fatal(e.Start(":8080"))
}
```

> ທົດສອບກົດທີ່ລິ້ງ Logged in with facebook

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rljbrjq8tym22kte3g5x.png)

> ຖ້າບໍ່ມີຫາຫຍັງກໍຈະໄດ້ຜົນຫຼັບດັ່ງຮູບລຸ່ມນີ້

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/h81f8b8ipj8crie5bbsy.png)

> ເມື່ອເຮົາກົດ Continue ສັ່ງເກດເບິ່ງ Url Facebook ຈະທຳການ Redirect ກັບມາ Url ຕາມທີ່ເຮົາກຳນົດ

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pnn09k21j1r57ng2ayc3.png)

2/ ສ້າງ Service Callback ເພື່ອຮອງຮັບເພີ່ມໃນ file main.go

```
e.GET("/callback", handleCallback)
```
3/ ເພີ່ມຟັງຊັນ handleCallback ຖ້າເຮັດຖືກຕ້ອງສຳເລັດທຳການ return accessToken ອອກໄປ

```
func handleCallback(c echo.Context) error {
	code := c.QueryParam("code")
	if code == "" {
		return c.String(http.StatusBadRequest, "Authorization code not found.")
	}

	// Exchange the authorization code for an access token
	tokenURL := "https://graph.facebook.com/v17.0/oauth/access_token"
	params := url.Values{}
	params.Set("client_id", ClientID)
	params.Set("client_secret", ClientSecret)
	params.Set("redirect_uri", RedirectURL)
	params.Set("code", code)

	resp, err := http.PostForm(tokenURL, params)
	if err != nil {
		log.Println("Failed to exchange code for token:", err)
		return c.String(http.StatusInternalServerError, "Failed to exchange code for token.")
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		log.Println("Failed to exchange code for token:", resp.Status)
		return c.String(http.StatusInternalServerError, "Failed to exchange code for token.")
	}

	var tokenResp struct {
		AccessToken string `json:"access_token"`
	}
	dec := json.NewDecoder(resp.Body)
	if err := dec.Decode(&tokenResp); err != nil {
		log.Println("Failed to parse token response:", err)
		return c.String(http.StatusInternalServerError, "Failed to parse token response.")
	}

	return c.JSON(http.StatusOK, echo.Map{
		"accessToken": tokenResp.AccessToken,
	})
}
```

> ກໍຈະໄດ້ accessToken ຕອບຈາກ Facebook ແບບນີ້

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/go0yelexd0af8vh9onut.png)

4/ ຫຼັງຈາກທີ່ໄດ້ accessToken ແລ້ວເຮົາກໍນຳເອົາໄປ Get user ເພື່ອເອົາຂໍ້ມູນພື້ນຖານເຊັ່ນ: ລະຫັດ,ຊື່,ອີເມວ ໃຫ້ອັບເດດຟັງຊັນ handleCallback ດັ່ງນີ້

```
func handleCallback(c echo.Context) error {
	code := c.QueryParam("code")
	if code == "" {
		return c.String(http.StatusBadRequest, "Authorization code not found.")
	}

	// Exchange the authorization code for an access token
	tokenURL := "https://graph.facebook.com/v17.0/oauth/access_token"
	params := url.Values{}
	params.Set("client_id", ClientID)
	params.Set("client_secret", ClientSecret)
	params.Set("redirect_uri", RedirectURL)
	params.Set("code", code)

	resp, err := http.PostForm(tokenURL, params)
	if err != nil {
		log.Println("Failed to exchange code for token:", err)
		return c.String(http.StatusInternalServerError, "Failed to exchange code for token.")
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		log.Println("Failed to exchange code for token:", resp.Status)
		return c.String(http.StatusInternalServerError, "Failed to exchange code for token.")
	}

	var tokenResp struct {
		AccessToken string `json:"access_token"`
	}
	dec := json.NewDecoder(resp.Body)
	if err := dec.Decode(&tokenResp); err != nil {
		log.Println("Failed to parse token response:", err)
		return c.String(http.StatusInternalServerError, "Failed to parse token response.")
	}

	fmt.Println("tokenResp.AccessToken: ", tokenResp.AccessToken)

	userFacebook, err := getUserFacebook(c.Request().Context(), tokenResp.AccessToken)
	if err != nil {
		return c.JSON(http.StatusInternalServerError, err)
	}

	return c.JSON(http.StatusOK, echo.Map{
		"userFacebook": userFacebook,
	})
}
```
ຸ5/ ເພີ່ມຟັງ getUserFacebook 

```
type UserDetail struct {
	ID    string `json:"id"`
	Name  string `json:"name"`
	Email string `json:"email"`
}

func getUserFacebook(ctx context.Context, token string) (*UserDetail, error) {
	url := "https://graph.facebook.com/me?fields=id,name,email&access_token=" + token
	req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
	if err != nil {
		return nil, fmt.Errorf("failed to create request: %w", err)
	}

	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return nil, fmt.Errorf("failed to do request: %w", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
	}

	var user UserDetail
	dec := json.NewDecoder(resp.Body)
	if err := dec.Decode(&user); err != nil {
		return nil, fmt.Errorf("failed to decode response: %w", err)
	}

	return &user, nil
}
```

> ທົດສອບ Login ອີກຄັ້ງກໍຈະໄດ້ຜົນຫຼັບອອກມາແບບນີ້

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ivevvb8kkcd99cxdrxtx.png)

> ມາຮອດນີ້ຖືກວ່າສຳເລັດແລ້ວ 5555 ສຳລັບການທົດລອງສ້າງ OAuth Facebook ໃນ go Basic ເບື້ອງຕົ້ນກໍຈະມີປະມານນີ້, ມີອັນໃດຜິດພາດບໍ່ຄົບຖ້ວນກະຂໍອະໄພນຳເດີຜູ້ຂຽນເອງກໍຫາເລີ່ມໃຊ້ຄືກັນສາມາດແນະນຳເພີ່ມຕື່ມໄດ້

_ຂອບໃຈທີ່ອ່ານຈົບ_