15
14

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Go4Advent Calendar 2019

Day 2

golang/oauth2を使ってgithubのOAuth認証用APIサーバを作ってみる(WIP)

Last updated at Posted at 2019-12-01

はじめに

この記事は、Go4 アドベントカレンダーの2日目になります。
OAuthの認証ってどうやって実装するのだろう。
golang/oauth2: Go OAuth2があることは知っていましたが、実際に触ったことがなかったので、今回認証用のAPIサーバを作ってみようと思いました。
時間がなくてまだWIPです。。。
hirano00o/github-oauth-sample

golang/oauth2の使い方

oauth2/example_test.go at master · golang/oauth2 を見ると、使い方がわかります。

簡単にまとめるとこんな感じ。

  1. conf := &oauth2.Config{} にGithubのCLIENT IDやSECRET、エンドポイントを詰める。
    エンドポイントは、oauth2/github.go at master · golang/oauth2importして利用する。
    このconfは、トークンを取得するときや、APIにアクセスするときにも利用する。
  2. conf.AuthCodeURL()でCSRF対策用のstate等を入れて、GithubにログインするためのURLを作成する。
    このURLにアクセスすると、Githubのログイン画面が出力され、ログインするとコールバック用のURLにリダイレクトされる。
    コールバック用のURLは、CLIENT IDやSECRETを作成したときに設定したもの。
  3. コールバック用URLにリダイレクトされたリクエストには、codestateがformに設定されている。
    このstateが、AUthCodeURL()で設定したstateとイコールかを確認し、イコールであればcodeを基にconf.Exchange()でトークンを発行する。
    Exchange()で発行したトークンは、構造体であり、access tokentoken type, refresh token, expiryが入っている。
  4. このトークンとコンテキストでconf.Client()からhttpClientを作り、GetやPost等を行う。
    ちなみに、conf.Client()したとき、TokenSource()が呼ばれ、トークンのリフレッシュが必要なときは、自動的にリフレッシュされる。

フロー

登場人物は

  • ユーザと
  • フロント用サーバ
  • 認証用APIサーバ(今回作っているもの)

です。
コールバック先は、フロント用サーバとしています。

  1. ユーザがフロント用サーバにアクセスする。フロント用サーバはセッションIDを払い出しCookieに詰める。
  2. ユーザがログインボタンを押すと、フロント用サーバは、セッションIDを取得し、認証用APIサーバにセッションIDを渡す。
  3. 認証用サーバは、ランダム値のstateを作成、githubログイン用URLを発行する。stateとセッションIDを紐付けてDBに入れる。
  4. フロント用サーバは、ユーザを返却されたログイン用URLにリダイレクトさせる。
  5. ユーザがログインに成功するとコールバックされ、フロント用サーバはcodestateを取得する。
  6. フロント用サーバは、codestate、セッションIDを認証用APIサーバに渡す。
  7. 認証用APIサーバは、stateがURL発行時のものとイコールか確認し、イコールであれば、codeからgithub用のトークンを作成する。
    また、ユーザ用のトークンを作成し、これとgithub用のトークンを紐付けてDBに保存し、ユーザ用のトークンのみ返却する。
  8. フロント用サーバは、トークンをCookieに詰めてユーザに返す。

(例えば)この後に、リポジトリ一覧を取得する場合は、

  1. フロント用サーバがCookieからトークンを取得し、認証用APIサーバに渡す。
  2. 認証用APIサーバは、トークンの期限を確認し、トークンと紐づくgithub用のトークンを返す。
  3. フロント用サーバは、返却されたgithub用のトークンでリポジトリ一覧を取得する

ユーザ用のトークンとか無しで、セッションIDとgithub用のトークンを紐付ければよかったか...

コード(WIP)

Clean Architectureを意識して作ってみています。
hirano00o/github-oauth-sample

ディレクトリ構成
.
├── domain
│   └── domain.go
├── go.mod
├── go.sum
├── infrastructure
│   ├── config.go
│   ├── dbhandler.go
│   └── router.go
├── interfaces
│   ├── controllers
│   │   ├── context.go
│   │   ├── controller.go
│   │   └── error.go
│   ├── database
│   │   ├── database.go
│   │   └── repository.go
│   └── handler.go
├── main.go
├── mysql
│   └── init.sql
├── swagger.yaml
└── usecases
    └── usecase.go

よく利用するoauth2.Configは、infrastructure層のconfig.goで環境変数を読み込むときに一緒に作り、コンフィグとして読み込んでいます。

infrastructure/config.go
func getGithubConf() (github oauth2.Config) {
        scopes := []string{"repo"}
        github = oauth2.Config{
                ClientID:     os.Getenv("GITHUB_CLIENT_ID"),
                ClientSecret: os.Getenv("GITHUB_CLIENT_SECRET"),
                RedirectURL:  os.Getenv("SERVER_HOST"),
                Scopes:       scopes,
                Endpoint:     oauth2github.Endpoint,
        }
        return
}

そして読み込んだコンフィグは、interface層controllerに渡して利用しています。
正直どこで作るべきか悩みましたが、毎回どこかで作るよりかは...ってことでコンフィグ読み込み時に一緒にしました。
usecase層まで足を伸ばしているのが、微妙に感じるけど問題ないのか? state入りのURL作ったり、トークン発行するためには必要だけども。

infrastructure/router.go
func Router() *gin.Engine {
        conf := NewConf()
        controller := controllers.NewController(NewDB(conf.DBConf.Database, conf.DBConf.DSN))
        router := gin.Default()
        v1 := router.Group("/v1")
        auth := v1.Group("/auth")
        github := auth.Group("/github")
        github.GET("/login", func(c *gin.Context) { controller.Login(c, conf) })
        github.GET("/callback", func(c *gin.Context) { controller.Callback(c, conf) })
        github.GET("/token", func(c *gin.Context) { controller.Auth(c) })
        zap.S().Info("running")
        return router
}

おわりに

なるべく早く完成させます。
DBは、Redisに変更したいし。テスト作らないと。。。

15
14
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
15
14

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?