Help us understand the problem. What is going on with this article?

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

はじめに

この記事は、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に変更したいし。テスト作らないと。。。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした