筆者:重松 亜夢
はじめまして!香川大学の重松亜夢です。2019年秋にSLPの所長を引き継ぎま
した。SLPの最近の主な活動はチーム開発です。2019年12月には部員が最近の活動をブログに投稿し、Advent Calendarを作成しました。また、2020年1月には餅つきで親交を深めました。
今回は、Webアプリケーションに認証機能を実装します。具体的には、米Google社の「Google Cloud Platform」のAPIを使って「Googleでログイン」を実装します。
シェルスクリプトマガジン Vol.65は以下のリンク先でご購入できます。![]()
![]()
図9 「.env」ファイルに記述する内容
SAMPLE_HOST="localhost:1323"
GOOGLE_CLIENT_ID="クライアントID"
GOOGLE_CLIENT_SECRET="クライアントシークレット"
図10 「main.go」ファイルに記述する内容
package main
import (
route "example.com/user_name/sample-app/route"
)
func main() {
route.Echo.Logger.Fatal(route.Echo.Start(":1323"))
}
図11 「router.go」ファイルに記述する内容
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」ファイルに追記する内容
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」ファイルに記述する内容
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」ファイルに記述する内容
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」ファイルに追記する内容
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関数の変更コード
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」ファイルに追記する内容
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関数に挿入する内容
renderer := &TemplateRenderer{
templates: template.Must(template.ParseGlob("templates/*.html")),
}
e.Renderer = renderer
e.GET("/", handler.MainPageHandler)
図19 「handler.go」ファイルに記述する内容
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」ファイルに記述する内容
{{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」ファイルに記述する内容
{{define "head"}}
<title>{{ .title }} / Sample-App</title>
{{end}}
図23 「router.go」ファイルのinit関数定義部分に挿入する内容
e.Pre(middleware.RemoveTrailingSlash())
e.Group("", authCheckMiddleware())
図24 「router.go」ファイルの末尾に追記する内容
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 認証後だけアクセスできるルートを設定する例
authCheck := e.Group("", authCheckMiddleware())
authCheck.GET("/fuga", handler.SamplePage)