筆者:重松 亜夢
はじめまして!香川大学の重松亜夢です。2019年秋にSLPの所長を引き継ぎま
した。SLPの最近の主な活動はチーム開発です。2019年12月には部員が最近の活動をブログに投稿し、Advent Calendarを作成しました。また、2020年1月には餅つきで親交を深めました。
今回は、Webアプリケーションに認証機能を実装します。具体的には、米Google社の「Google Cloud Platform」のAPIを使って「Googleでログイン」を実装します。
シェルスクリプトマガジン Vol.65は以下のリンク先でご購入できます。
図9 「.env」ファイルに記述する内容
1 2 3 |
SAMPLE_HOST="localhost:1323" GOOGLE_CLIENT_ID="クライアントID" GOOGLE_CLIENT_SECRET="クライアントシークレット" |
図10 「main.go」ファイルに記述する内容
1 2 3 4 5 6 7 |
package main import ( route "example.com/user_name/sample-app/route" ) func main() { route.Echo.Logger.Fatal(route.Echo.Start(":1323")) } |
図11 「router.go」ファイルに記述する内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
package route import ( "fmt" "log" "os" "example.com/user_name/sample-app/handler" "github.com/joho/godotenv" "github.com/labstack/echo/v4" "github.com/labstack/echo/v4/middleware" "github.com/stretchr/gomniauth" "github.com/stretchr/gomniauth/providers/google" "github.com/stretchr/signature" ) var Echo *echo.Echo func init() { e := echo.New() err := setupOAuth() if err != nil { log.Fatal("Error loading .env file") } e.Use(middleware.Logger()) e.GET("/auth/login/:provider", handler.LoginHandler) e.GET("/auth/callback/:provider", handler.CallbackHandler) Echo = e } |
図12 「router.go」ファイルに追記する内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
func setupOAuth() error { err := godotenv.Load() if err != nil { return err } host := os.Getenv("SAMPLE_HOST") googleCallbackURL := fmt.Sprintf("http://%s/auth/callback/google", host) gomniauth.SetSecurityKey(signature.RandomKey(64)) gomniauth.WithProviders( google.New( os.Getenv("GOOGLE_CLIENT_ID"), os.Getenv("GOOGLE_CLIENT_SECRET"), googleCallbackURL, ), ) return nil } |
図13 「sesseion.go」ファイルに記述する内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
package handler import ( "net/http" "github.com/labstack/echo/v4" "github.com/stretchr/gomniauth" "github.com/stretchr/objx" ) func LoginHandler(c echo.Context) error { provider, err := gomniauth.Provider(c.Param("provider")) if err != nil { return err } authURL, err := provider.GetBeginAuthURL(nil, nil) if err != nil { return err } return c.Redirect(http.StatusTemporaryRedirect, authURL) } |
図13 「sesseion.go」ファイルに記述する内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
package handler import ( "net/http" "github.com/labstack/echo/v4" "github.com/stretchr/gomniauth" "github.com/stretchr/objx" ) func LoginHandler(c echo.Context) error { provider, err := gomniauth.Provider(c.Param("provider")) if err != nil { return err } authURL, err := provider.GetBeginAuthURL(nil, nil) if err != nil { return err } return c.Redirect(http.StatusTemporaryRedirect, authURL) } |
図14 「sesseion.go」ファイルに追記する内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
func CallbackHandler(c echo.Context) error { provider, err := gomniauth.Provider(c.Param("provider")) if err != nil { return err } omap, err := objx.FromURLQuery(c.QueryString()) if err != nil { return err } _, err = provider.CompleteAuth(omap) if err != nil { return err } return c.String(http.StatusOK, "Login Success!") } |
図16 「sesseion.go」ファイルのCallbackHandler関数の変更コード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
creds, err := provider.CompleteAuth(omap) if err != nil { return err } user, err := provider.GetUser(creds) if err != nil { return err } authCookieValue := objx.New(map[string]interface{}{ "name": user.Name(), "email": user.Email(), "avatarURL": user.AvatarURL(), }).MustBase64() cookie := &http.Cookie{ Name: "auth", Value: authCookieValue, Path: "/", Expires: time.Now().Add(24 * time.Hour), } c.SetCookie(cookie) return c.Redirect(http.StatusTemporaryRedirect, "/") |
図17 「router.go」ファイルに追記する内容
1 2 3 4 5 6 7 8 |
type TemplateRenderer struct { templates *template.Template } func (t *TemplateRenderer) Render(w io.Writer, name string, data interface{}, c echo.Context) error { return t.templates.ExecuteTemplate(w, name, data) } |
図18 「router.go」ファイルのinit関数に挿入する内容
1 2 3 4 5 |
renderer := &TemplateRenderer{ templates: template.Must(template.ParseGlob("templates/*.html")), } e.Renderer = renderer e.GET("/", handler.MainPageHandler) |
図19 「handler.go」ファイルに記述する内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
package handler import ( "net/http" "github.com/labstack/echo/v4" "github.com/stretchr/objx" ) func MainPageHandler(c echo.Context) error { auth, err := c.Cookie("auth") if err != nil { return c.Render(http.StatusOK, "welcome", map[string]interface{}{ "title": "Welcome", }) } userData := objx.MustFromBase64(auth.Value) return c.Render(http.StatusOK, "top", map[string]interface{}{ "name": userData["name"], "email": userData["email"], "avatarURL": userData["avatarURL"], "title": "TopPage", }) } |
図20 「index.html」ファイルに記述する内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
{{define "top"}} {{template "head" .}} <img src={{.avatarURL}} width="10%"> <h2>Hello, {{.name}}</h2> Your Email : {{.email}} {{end}} {{define "welcome"}} {{template "head" .}} <h1>ようこそ</h1> <ul> <li> <a href="/auth/login/google">Googleでログイン</a> </li> </ul> {{end}} |
図21 「base.html」ファイルに記述する内容
1 2 3 |
{{define "head"}} <title>{{ .title }} / Sample-App</title> {{end}} |
図23 「router.go」ファイルのinit関数定義部分に挿入する内容
1 2 |
e.Pre(middleware.RemoveTrailingSlash()) e.Group("", authCheckMiddleware()) |
図24 「router.go」ファイルの末尾に追記する内容
1 2 3 4 5 6 7 8 9 10 11 |
func authCheckMiddleware() echo.MiddlewareFunc { return func(next echo.HandlerFunc) echo.HandlerFunc { return func(c echo.Context) error { _, err := c.Cookie("auth") if err != nil { return c.Redirect(http.StatusTemporaryRedirect, "/") } return next(c) } } } |
図25 認証後だけアクセスできるルートを設定する例
1 2 |
authCheck := e.Group("", authCheckMiddleware()) authCheck.GET("/fuga", handler.SamplePage) |