Auth0×Golangで認証認可の処理を実施する。(チュートリアル Hands-on)

## はじめに
IDaaSのサービスとして注目を集めているAuth0。プロダクト作成にあたり必須の認証認可処理を短時間で高品質なモノを作れる。[前回記事](https://qiita.com/kouji0705/items/b80f7cc7316324757ee4)では、フロントの認証処理の記事を書いたので、今回はバックエンドの認可処理の機能を作成する。

### Auth0の仕組み
①ユーザ認証：フロント⇄Auth０で完結。
②認可処理：Auth0⇄バックエンドで完結。

①ユーザ認証
　フロント側でユーザIDとパスワードを入力し、Auth０でユーザ認証を実施する。承認済みのユーザであればJWTが返却され、それ以外であれば401エラーが発生する。フロント側ではJWTを元にAPIを叩けるかの認可処理が実施される。
②認可処理
ユーザ認証時に取得したJWTを基にAPIを実行できるかを実施する。このJWTには、ユーザ権限やユーザIDも含めることができるため、アクセスコントロールが実施できる。

認証認可が苦手な人は下記記事を見ておくとわかりやすいかと。

https://service.plan-b.co.jp/blog/tech/27748/

## Hands-onスタート
Hands-onは公式のQuickStartを参考に作成しております。

### Auth0の設定をする
1. Auth0でAPIを作る。
![スクリーンショット 2022-05-03 13.55.11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/599049/31574dad-fd7f-0b23-4706-c9c8707ce503.png)
→ここでは、「QuickStarts」という名前で、Identifierは「https://quickstarts/api」

2. Permissionを作成する。
![スクリーンショット 2022-05-03 13.58.12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/599049/d92eb8a6-e435-776d-c1f6-ab64cd49b79e.png)
![スクリーンショット 2022-05-03 13.58.21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/599049/33a52560-0b88-20c0-7d35-57bd482ba0c7.png)
→権限を設定する。

### Golangでアプリケーションを作成する
```shell:ディレクトリ構成
~/go/src/auth0_go $ tree -Ia .git 
.
├── .env
├── go.mod
├── go.sum
├── main.go
└── middleware
    └── jwt.go

1 directory, 5 files
```


```go:main.go
// main.go

package main

import (
	"log"
	"net/http"

	"github.com/joho/godotenv"

	"auth0_go/middleware"
)

func main() {
	if err := godotenv.Load(); err != nil {
		log.Fatalf("Error loading the .env file: %v", err)
	}

	router := http.NewServeMux()

	// This route is always accessible.
	router.Handle("/api/public", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "application/json")
		w.WriteHeader(http.StatusOK)
		w.Write([]byte(`{"message":"Hello from a public endpoint! You don't need to be authenticated to see this."}`))
	}))

	// This route is only accessible if the user has a valid access_token.
	router.Handle("/api/private", middleware.EnsureValidToken()(
		http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			// CORS Headers.
			w.Header().Set("Access-Control-Allow-Credentials", "true")
			w.Header().Set("Access-Control-Allow-Origin", "http://localhost:3000")
			w.Header().Set("Access-Control-Allow-Headers", "Authorization")

			w.Header().Set("Content-Type", "application/json")
			w.WriteHeader(http.StatusOK)
			w.Write([]byte(`{"message":"Hello from a private endpoint! You need to be authenticated to see this."}`))
		}),
	))

	log.Print("Server listening on http://localhost:3010")
	if err := http.ListenAndServe("0.0.0.0:3010", router); err != nil {
		log.Fatalf("There was an error with the http server: %v", err)
	}
}
```
```go:middleware/jwt.go
// middleware/jwt.go

package middleware

import (
	"context"
	"log"
	"net/http"
	"net/url"
	"os"
	"time"

	jwtmiddleware "github.com/auth0/go-jwt-middleware/v2"
	"github.com/auth0/go-jwt-middleware/v2/jwks"
	"github.com/auth0/go-jwt-middleware/v2/validator"
)

// CustomClaims contains custom data we want from the token.
type CustomClaims struct {
	Scope string `json:"scope"`
}

// Validate does nothing for this example, but we need
// it to satisfy validator.CustomClaims interface.
func (c CustomClaims) Validate(ctx context.Context) error {
	return nil
}

// 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)
	}
}
```
```:.env
# The URL of our Auth0 Tenant Domain.
# If you're using a Custom Domain, be sure to set this to that value instead.
AUTH0_DOMAIN='YOUR_DOMAIN'

# Our Auth0 API's Identifier.
AUTH0_AUDIENCE='YOUR_API_IDENTIFIER'
```

### API実行する。
APIはJWTを取得前提のため、curlコマンド等でJWTを取得する必要あり。そのため、下記のようなコマンドでJWTを取得する。
①JWTを取得する
```shell:curl
curl --request POST \
  --url https://kkfactory.jp.auth0.com/oauth/token \
  --header 'content-type: application/json' \
  --data '{"client_id":"XXXXXXX","client_secret":"XXXXXXX","audience":"XXXXXXX","grant_type":"client_credentials"}'

■実行結果
  {"access_token":"XXXXXXXX","expires_in":86400,"token_type":"Bearer"}%       
```
このアクセストークンを基にAPIを実行する。

②main.goの実行
```go:main.goの実行
~/go/src/auth0_go $ go run main.go       
2022/05/03 15:27:30 Server listening on http://localhost:3010
```

③APIを叩いてみる。
```shell:API成功時(JWTが正しい)
curl --request GET \
  --url http://path_to_your_api/ \
  --header 'XXXXXX'

■実行結果
{
  "message": "Hello from a private endpoint! You need to be authenticated to see this."
}
```

```shell:API成功時(JWTが不正)
curl --request GET \
  --url http://path_to_your_api/ \
  --header 'XXXXXX'

■実行結果
{
  "message": "Failed to validate JWT."
}
```


## 参照
https://auth0.com/docs/quickstart/backend/golang/01-authorization#validate-access-tokens