[Auth0] Auth0+Golang APIでクイックスタートやってみた
こんにちは。
ゲームソリューション部の西川です。
Auth0を学習する機会があったので、チュートリアルがてら公式ドキュメント記載のクイックスタートを行ってみました。
Auth0関連の記事はDevelopersIOにも溢れているとは思いますが、API(バックエンド)のクイックスタートを触っている方は少ないと感じたので、やってみました。
まずは、Auth0の概要です。
Auth0とは
Auth0はIDaaS(Identity as a Service)です。
簡単に言うと、誰でも簡単に認証認可を導入できるサービスです。
トークンの管理やセキュリティ周りなどを意識せずに実装することができます。
認証認可周りを自力で導入しようとすると、認証認可・トークン管理・セキュリティ・OAuthなどの実装を考え、かつ、それぞれのライブラリを利用する場合は、脆弱性を確認して定期的にアップデートをして・・・
のように実装面だけでなく、運用面でのコストも大きくなることかと思います。
その点、Auth0を利用することで、それらを一括して管理することができるため、開発者はサービスのロジックの実装に専念することができます。
それでは早速、クイックスタートの手順を確認しながら構築してみます。
やってみた
今回はクイックスタートの手順どおりに、APIでの認証認可を実装します。
イメージとしてはReactなどのフロントエンドでログインなどにアクセストークンを取得し、APIに送ることでトークンの認証を行い、APIのアクセス制御を行えるようなものです。
APIの作成
まずは、今回のクイックスタートで使用するAPIを作成します。
APIsのCreate APIより作成できます。
Name: API名を入力
Identifier: 一意の識別子を入力(URLを入れるような記載になっているが、一意の識別子であればなんでも問題なさそう)
JSON Web Token (JWT) Profile: トークンの形式だったりを指定できる(特にこだわりがなければAuth0で良いと思われる)
JSON Web Token (JWT) Signing Algorithm: トークン発行時のアルゴリズムを指定できる(RS256かHS256を指定できる。今回は基本的に推奨されている非対称アルゴリズムのRS256を選択)
Createを押下し、作成を行います。
クイックスタート
APIの作成が完了したら、クイックスタートの手順を確認しながら実際のソースコードを見ていきます。
Golangのクイックスタートはこちら。
①Define permissions
まずは、権限を設定します。
APIの設定ページのPermissionsタブにてリソースへのアクセスの権限を設定できます。
手順通り、read:messages
を追加しましょう。
今回こちらの権限は、後述で作成する/api/private-scoped
のエンドポイントを実行した際に使用します。
permissionsはユーザーごとにアクセス制御を行いたいときなど、ロールに権限を設定することで使用できます。
②Install dependencies
必要なDependenciesをインストールします。
クイックスタート記載の手順とサンプルコードのREADME.mdの記載が異なるので、
README記載の手順通りにコマンドを実行します。
go mod vendor
エラー等出ていなければ問題なく完了しています。
③Configure your application
.env
を作成して、AUTH0_DOMAIN
とAUTH0_AUDIENCE
を設定します。
それぞれ、AUTH0_DOMAIN
はTenantのドメインを設定し、AUTH0_AUDIENCE
はAPIの設定画面から確認できるIdentifierを設定します。
サンプルコードをダウンロードした際に作成したAPIを指定していれば.env
にすでに値が設定されています。
④Create a middleware to validate access tokens
アクセストークンを認証するためのミドルウェアを作成します。
サンプルコードではmiddleware/jwt.go
に配置されています。
EnsureValidTokenにてトークンの認証を行います。
トークンが無効な場合は401エラーを返すようになっています。
// (略)
// EnsureValidToken is a middleware that will check the validity of our JWT.
func EnsureValidToken() func(next http.Handler) http.Handler {
issuerURL, err := url.Parse("https://" + os.Getenv("AUTH0_DOMAIN") + "/")
if err != nil {
log.Fatalf("Failed to parse the issuer url: %v", err)
}
provider := jwks.NewCachingProvider(issuerURL, 5*time.Minute)
jwtValidator, err := validator.New(
provider.KeyFunc,
validator.RS256,
issuerURL.String(),
[]string{os.Getenv("AUTH0_AUDIENCE")},
validator.WithCustomClaims(
func() validator.CustomClaims {
return &CustomClaims{}
},
),
validator.WithAllowedClockSkew(time.Minute),
)
if err != nil {
log.Fatalf("Failed to set up the jwt validator")
}
errorHandler := func(w http.ResponseWriter, r *http.Request, err error) {
log.Printf("Encountered error while validating JWT: %v", err)
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusUnauthorized)
w.Write([]byte(`{"message":"Failed to validate JWT."}`))
}
middleware := jwtmiddleware.New(
jwtValidator.ValidateToken,
jwtmiddleware.WithErrorHandler(errorHandler),
)
return func(next http.Handler) http.Handler {
return middleware.CheckJWT(next)
}
}
併せて、トークンがリソースへアクセスするためのスコープ(権限)を持っているかどうか判別する仕組みを作成します。
HasScopeにてトークンのスコープを確認します。
// (略)
// HasScope checks whether our claims have a specific scope.
func (c CustomClaims) HasScope(expectedScope string) bool {
result := strings.Split(c.Scope, " ")
for i := range result {
if result[i] == expectedScope {
return true
}
}
return false
}
// (略)
⑤Protect API endpoints
各エンドポイントに④で作成したミドルウェアを設定して、アクセス制御を行います。
エンドポイントごとのルートの設定や、アクセス制御の実装はrouter/router.go
あたりに記載されています。
各エンドポイントごとの説明は以下になります。
/api/public: 特に制限しません。
/api/private: EnsureValidTokenを設定して、トークンによる認証を必要にします。
/api/private-scoped: EnsureValidTokenおよび、HasScopeを設定して、トークン認証+トークンの権限を確認します。
APIの呼び出し
それでは、APIを実際に呼び出します。
下記のコマンドでAPIを立ち上げておきます。
go run main.go
APIの呼び出し方法は下記のようにパスとauthorizationにアクセストークンを渡す形になります。
curl --request GET \
--url http://your-domain.com/api_path \
--header 'authorization: Bearer YOUR_ACCESS_TOKEN_HERE'
API設定のTestタブからテスト用のアクセストークンを取得できます。
呼び出し例
/api/public
curl --request GET --url http://localhost:3010/api/public'
{"message":"Hello from a public endpoint! You don't need to be authenticated to see this."}
/api/private
curl --request GET --url http://localhost:3010/api/private --header 'authorization: Bearer <Accsess Token>'
{"message":"Hello from a private endpoint! You need to be authenticated to see this."}
/api/private-scoped
curl --request GET --url http://localhost:3010/api/private-scoped --header 'authorization: Bearer <Access Token>'
{"message":"Hello from a private endpoint! You need to be authenticated to see this."}
問題なく、APIの呼び出しを行うことができました。
もし/api/private-scoped
の呼び出しを行った際にエラーメッセージが出る場合は、以下のMachine To Machine Applicationsタブにてpermissionsのチェックがついているか確認してください。
さいごに
以上、クイックスタート通りにGolangのAPIでアクセストークンの認証を行うことができました。
ミドルウェアの実装は手間がかかるので、公式のサンプルコードがあると助かりますね。
次回はフロントエンド系のクイックスタートを行って、今回作成したGolangのAPIと繋ぎ込みを行ってみたいと思います。