こんばんは。七色メガネです。
今回はGO言語で、gomniauth というライブラリを使用して認証機能の実装を行ってみたいと思います。
このライブラリを使用することで、Google や Github などの正式プロバイダに登録しているアカウントを利用して、自分のアプリに認証機能を実装することが出来るようになります。
これは前の記事で触れたチャット・アプリの認証機能を担う箇所になります。
ただし今回は認証機能単品で実装を行っているので、チャット・アプリと直接の関係はありません。
gomniauth とは?
gomniauth とは、GOプログラムのために開発された認証フレームワークです。
OAuth2を初めとする、多くの認証プロトコルを採用しています。
またGoogleやFacebook、Githubなどの多くのプロバイダとの認証をサポートしているフレームワークです。
※公式doc https://github.com/stretchr/gomniauth/blob/master/README.md
OAuth とは?
Oauth とは、SNSやWebサービス間で「アクセス権限の認可」を行うためのプロコトルです。現在はOAuth2.0が標準規格となっています。
例えば Twitter にログインを行いたい(サインインしたい)という場合、普通はTwitterのアカウントを利用してログインを行いますね。しかしログインしたいサイトが増えてくると、サイトの数だけアカウントを作成しなくてはならず、クライアントの負担がとても大きくなってきます。
そこで利用できるのがOAuthです。
よくサービスのログイン画面で、そのサービス以外のアカウントでのログイン、例えば「Facebook のアカウントでログインする」などの項目をみたことがありませんか?おそらくそれは、OAuthの技術によって実装されています。
Facebookでログイン、の例で考えてみましょう。
ログインに必要な情報が要求されている時にこの[ Facebookでログイン ]を選択すると、サービスのプログラムがFacebookの認証サーバに、Facebookに登録されているクライアント情報を取得して良いかを問い合わせます(アクセストークンの要求)。この時クライアントのブラウザにはFacebookの認証画面が表示されます。そこでクライアントが認証を許可すると、Facebookのリソースサーバ(クライアント情報が格納されているサーバ)へのアクセスを行うことができるアクセストークンが発行され、Facebookに登録された情報で対象のサービスにログインができるようになります。
簡単にまとめて仕舞えば、他のサービスに登録されている情報を安全に使用して別サービスにログインするための技術、それがOAuthと言えそうです。
認証機能の実装
では実際にgomniauthを利用して、認証機能を実装してみましょう。
仕様
今回の認証機能では、次のような仕様に基づいて実装を行ってみます。
- ログイン画面と、ログイン後画面の2画面からなるアプリを作成する。
- ログイン画面では、Github あるいは Google のアカウントを利用して認証を行うことができる。
- ログイン後の画面では、認証したプロバイダに登録された情報に基づき、ユーザの名前とプロフィール画像を取得して表示する。
- ログイン後の画面ではログアウトすることができ、ログアウト後には認証情報を削除し、再度ログイン画面に遷移するようにする。
実装
では実装を行います。ここではgomniauthの使用にフォーカスを当ててソースの説明を行います。
ソース全文は、ページ下部のgithubのリンクからご参照ください。
認証機能実装に必要な大まかな工程は次のようになります。
- gomniauth でセキュリティキーを設定し、認証を行いたいプロバイダのモデルを作成する。
- プロバイダ毎の認証ページに遷移するための仕組みを作成する。
- プロバイダ毎の認証ページからコールバックされた後、プロバイダから提供された情報を取得する仕組みを作成する。
gomniauth でセキリュティキーを設定し、認証を行いたいプロバイダのモデルを作成する。
まずはgomniauthにセキュリティ・キーとプロバイダを登録します。
- SetSecurityKey() で、セキュリテイ・キーを登録します。これはユーザが自由に決定した値で構いません。
- WithProviders() で、利用するプロバイダーを登録します。今回は google と github を利用したいので、その二つのモデルを生成します。
生成の方法は、 .New() し、その中に
・プロバイダ用のクライアントID
・プロバイダ用のセキュリティキー
・プロバイダからのコールバック用のURL
を指定します。
最初の二つは各プロバイダから提供されるものなので、これらは別途取得してください。今回はこれらのキーを、info.go にまとめて、それを参照しています。
最後のコールバックURLは、認証ページの次に遷移する先のURLです。
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 26 27 28 29 |
package main import ( "github.com/stretchr/objx" "github.com/stretchr/gomniauth/providers/github" "github.com/stretchr/gomniauth/providers/google" "github.com/stretchr/gomniauth" ) /* 各プロバイダ・モデルを生成する。認証情報は info.go に格納し、使用する。 */ func setAuthInfo() { gomniauth.SetSecurityKey(securityKey) gomniauth.WithProviders( google.New( googleClientId, googleClientSecurityKey, "http://localhost:8080/auth/callback/google", ), github.New( githubClientId, githubClientSecurityKey, "http://localhost:8080/auth/callback/github", ), ) } |
プロバイダ毎の認証ページに遷移するための仕組みを作成する。
前提として、ログイン画面で認証プロバイダを選択した時に
http://localhost:8080/auth/login/<プロバイダ名>
でアクセスされるものとします。下のハンドラは、/login に対するハンドラの一部となります。
ここではURLから選択されたプロバイダを特定し、gomniauth.Provider(“プロバイダ名”) でプロバイダ用のモデルを生成しています。
そうして生成されたモデルの .GetBeginAuthURL(nil, nil) を使用することでプロバイダ毎の認証URLが返却されますので、そのURLへリダイレクトします。
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 |
func authHandler(w http.ResponseWriter, r *http.Request) { // urlの分解。区切り文字は "/" segs := strings.Split(r.URL.Path, "/") action := segs[2] //処理内容。login あるいは callback. provider_name := segs[3] //プロバイダの名称。github あるいは google switch action { /* loginページから遷移した場合 */ case "login": // gomniauthを使用し、プロバイダ・モデルを生成する provider, err := gomniauth.Provider(provider_name) if err != nil { log.Fatalln("プロバイダの取得に失敗しました。") } // プロバイダ毎の認証ページへのurlを取得する loginUrl, err := provider.GetBeginAuthURL(nil, nil) if err != nil { log.Fatalln("認証ページの取得に失敗しました。") } // 提供された認証用のページへリダイレクトする w.Header().Set("Location", loginUrl) w.WriteHeader(http.StatusTemporaryRedirect) |
プロバイダ毎の認証ページからコールバックされた後、プロバイダから提供された情報を取得する仕組みを作成する。
プロバイダの認証URLで認証を終了すると、一番初めの .WithProviders() で登録したコールバック用のURLが返却されるので、それをキャッチするような仕組みを作成します。
キャッチした後は、以下の手順でユーザ情報を取得します。
・gomniauth.Provider() でプロバイダ・モデルを再度取得する。
・モデル.CompleteAuth() で認証情報を取得する。
・モデル.GetUser() でユーザ・オブジェクトを取得する。
・ユーザオブジェクトから、情報を取得する。
今回は取得した情報を、Cookieに詰めるようにしています。
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 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
func authHandler(w http.ResponseWriter, r *http.Request) { // urlの分解。区切り文字は "/" segs := strings.Split(r.URL.Path, "/") action := segs[2] //処理内容。login あるいは callback. provider_name := segs[3] //プロバイダの名称。github あるいは google switch action { /* loginページから遷移した場合 */ case "login": ... /* プロバイダの元での認証を終え、callbackされた場合 */ case "callback": // gomniauthを使用し、プロバイダ・モデルを生成する provider, err := gomniauth.Provider(provider_name) if err != nil { log.Fatalln("プロバイダの取得に失敗しました。") } // 提供されたURLから、認証に必要な情報を抜き出す。 creds, err := provider.CompleteAuth(objx.MustFromURLQuery(r.URL.RawQuery)) if err != nil { log.Fatalln("認証情報の取得に失敗しました", err) } // 認証情報を使用して、プロバイダからUserオブジェクトを取得する。 user, err := provider.GetUser(creds) if err != nil { log.Fatalln("ユーザ情報の取得に失敗しました。") } // プロバイダから提供されたuserオブジェクトより情報を抜き出す。 authCookieValue := objx.New(map[string]interface{}{ "name": user.Name(), //ユーザ名 "avatar_url": user.AvatarURL(), //プロフ画像のURL "provider": provider_name, }).MustBase64() // 抜き出した情報をクッキーに詰める。Nameは"auth" http.SetCookie(w, &http.Cookie{ Name: "auth", Value: authCookieValue, Path: "/after", }) // ログイン後の画面に遷移する w.Header()["Location"] = []string{"/after"} w.WriteHeader(http.StatusTemporaryRedirect) } } |
テスト
ちょっと局所的すぎましたが、以上のような操作で認証処理が実装できました。
上のコードに基づいたアプリの挙動をみてみます。
・初めにルートにアクセスした時にCookieを確認し、認証情報がなければ /loginページへ遷移します。
ここでは認証を行うプロバイダを選択します。
選択されると、 http://localhost:8080/auth/login/”プロバイダ名” のURLが発行され遷移します。
・Github を選択すると、Github用の認証ページに遷移します。これはプロバイダ・モデルの .GetBeginAuthURL() で取得されたURL先ですね。
ここで認証を行うと、設定したコールバック用のURLにリダイレクトされます。
・コールバック用のURLをキャッチしたら、ユーザ情報を表示する /after に遷移します。
今回はプロバイダから返却されたユーザ名とプロフィール画像をCookieに詰めるように設定していましたので、HTML側でそれを取得し、このページで表示するようにしています。
ここでは Github を選択しましたが、Githubに登録したユーザ名と画像が正常に取得できていることが確認できます。
Logout を選択すると、認証情報を削除し /login ページへリダイレクトします。
まとめ
今回は gomniauth を使用した認証機能の実装を行ってみました。
gomniauthを利用するとプロバイダ毎に用意された認証用APIを意識することなく認証を行うことができてしまうので、とても簡単に実装ができました。
次回はこの認証機能と、以前作成したwebsocketでのチャット機能を組み合わせて、チャットアプリを作っていきたいと思います。
以上です。ここまでご覧いただき、ありがとうございました!
今回使用したsrc
今回使用したsrcをgithubに上げておきます。
https://github.com/NanairoMegane/authTest