認証有/無 両方で動くactionを作る

goaを使っていてちょっと困ったことがありました。

goaの認証はこんな感じで設定しますよね

```
var _ = Resource("basic", func() {
	Description("This resource uses basic auth to secure its endpoints")
	DefaultMedia(SuccessMedia)

	Security(BasicAuth)

	Action("secure", func() {
		Description("This action is secure with the basic_auth scheme")
		Routing(GET("/basic"))
		Response(OK)
		Response(Unauthorized)
	})

	Action("unsecure", func() {
		Description("This action does not require auth")
		Routing(GET("/basic/unsecure"))
		NoSecurity()
		Response(OK)
	})
})

```

でも、 ``Security(BasicAuth)`` と ``NoSecurity()``で、
認証あり/なしどちらかのルートしか設定できないように見えます。

例えば認証あり/なしで共通のルートを作りたいと考えました
(認証ありの場合は追加の情報を足して返すようにしたい等)

[slack](https://gophers.slack.com/messages/goa-jp/)で相談したら解決方法を教えていただいたので実装を纏めてみたいと思います。

結論から言うと、実装は以下においています
https://github.com/m0a-mystudy/goa-optional-token

# basic認証の場合

以下のようにgoaで設計します。


```
package design

import (
	. "github.com/goadesign/goa/design"
	. "github.com/goadesign/goa/design/apidsl"
)

// BasicAuth defines a security scheme using basic authentication.
var BasicAuth = BasicAuthSecurity("basic_auth")

// OptionalBasicAuth defines is all user access
var OptionalBasicAuth = BasicAuthSecurity("optional_basic_auth")

var _ = Resource("basic", func() {
	Description("This resource uses basic auth to secure its endpoints")
	DefaultMedia(SuccessMedia)

	Security(BasicAuth)

	Action("secure", func() {
		Description("This action is secure with the basic_auth scheme")
		Routing(GET("/basic"))
		Response(OK)
		Response(Unauthorized)
	})
	Action("optional", func() {
		Description("This action is optional secure with the basic_auth scheme")
		Security(OptionalBasicAuth)
		Routing(GET("/basic/optional"))
		Response(OK)
	})

	Action("unsecure", func() {
		Description("This action does not require auth")
		Routing(GET("/basic/unsecure"))
		NoSecurity()
		Response(OK)
	})
})
```

``basic/optional``のルートを追加しており
こちらについて認証あり/なし 関係無しでアクセスできるようにします。

ただし認証ありならtokenをcontextに登録し後から取得できるようにしておきます。

まず認証有り無し関係なしでも処理が継続できるようにするための``OptionalBasicAuth``を追加します。


``OptionalBasicAuth``用のmiddlewareを書きます。

```
type OptionalBasicAuth struct {
	User string
	Pass string
}

type optionalBasicAuthKeyType int

const (
	optionalBasicAuthKey optionalBasicAuthKeyType = iota + 1
)


func NewOptinalBasicAuthMiddleware() goa.Middleware {
	return func(h goa.Handler) goa.Handler {
		return func(ctx context.Context, rw http.ResponseWriter, req *http.Request) error {
			// Retrieve and log basic auth info
			user, pass, ok := req.BasicAuth()
			// A real app would do something more interesting here
			if !ok {
				goa.LogInfo(ctx, "failed basic auth")
				return h(ctx, rw, req) // 処理継続するためにnextHandlerを呼び出す。
			}

			// Proceed
			goa.LogInfo(ctx, "basic", "user", user, "pass", pass)
			ctx = context.WithValue(ctx, optionalBasicAuthKey, &OptionalBasicAuth{
				User: user,
				Pass: pass,
			})
			return h(ctx, rw, req)
		}
	}
}

// contextからbasic認証時のuser passを取得する
func ContextOptionalBasicAuth(ctx context.Context) *OptionalBasicAuth {
	if v := ctx.Value(optionalBasicAuthKey); v != nil {
		return v.(*OptionalBasicAuth)
	}
	return nil
}
```

上記のようにbasic認証失敗時でも処理を継続するように変更します
basic認証成功時はcontextに認証時の情報を保存しておきます。

実際に使う側では以下のようにmiddlewareの呼び出しを追加するのを忘れないようにして下さい

```
app.UseBasicAuthMiddleware(service, NewBasicAuthMiddleware())
app.UseOptionalBasicAuthMiddleware(service, NewOptinalBasicAuthMiddleware()) // <-- 追加
```


Action内の実装は以下のとおりです

```
// Optional runs the secure action.
func (c *BasicController) Optional(ctx *app.OptionalBasicContext) error {
	fmt.Println("in Optional", ContextOptionalBasicAuth(ctx))
	res := &app.Success{OK: true}
	return ctx.OK(res)
}
```
上記が追加されたActionで
 ``ContextOptionalBasicAuth`` 関数 にて contextからtokenが取得できます。

# jwt認証の場合

基本的には同じ対応です。

先ず追加のルート設計から

```
var JWT = JWTSecurity("jwt", func() {
	Header("Authorization")
	Scope("api:access", "API access") // Define "api:access" scope
})

// 追加
var OptionalJWT = JWTSecurity("optional_jwt", func() {
	Header("Authorization")
	Scope("api:access", "API access") // Define "api:access" scope
})

var SigninBasicAuth = BasicAuthSecurity("SigninBasicAuth")

// Resource jwt uses the JWTSecurity security scheme.
var _ = Resource("jwt", func() {
	Description("This resource uses JWT to secure its endpoints")
	DefaultMedia(SuccessMedia)

	Security(JWT, func() { // Use JWT to auth requests to this endpoint
		Scope("api:access") // Enforce presence of "api" scope in JWT claims.
	})

	Action("signin", func() {
		Description("Creates a valid JWT")
		Security(SigninBasicAuth)
		Routing(POST("/jwt/signin"))
		Response(NoContent, func() {
			Headers(func() {
				Header("Authorization", String, "Generated JWT")
			})
		})
		Response(Unauthorized)
	})

	Action("secure", func() {
		Description("This action is secured with the jwt scheme")
		Routing(GET("/jwt"))
		Params(func() {
			Param("fail", Boolean, "Force auth failure via JWT validation middleware")
		})
		Response(OK)
		Response(Unauthorized)
	})

// 追加
	Action("optional", func() {
		Description("This action is secured with the jwt scheme")
		Routing(GET("/jwt/optional"))
		Security(OptionalJWT, func() {
			Scope("api:access")
		})

		Response(OK)
	})

	Action("unsecure", func() {
		Description("This action does not require auth")
		Routing(GET("/jwt/unsecure"))
		NoSecurity() // Override the need for auth
		Response(OK)
	})
})

```
``jwt/optional``のルートを追加しており
こちらについて認証あり/なし 関係無しでアクセスできるようにします。


middlewareが若干面倒くさかったです。

```
// NewOptionalJWTMiddleware creates a middleware that checks for the presence of a JWT Authorization header
func NewOptionalJWTMiddleware() (goa.Middleware, error) {
	keys, err := LoadJWTPublicKeys()
	if err != nil {
		return nil, err
	}
	jwtMiddleware := jwt.New(jwt.NewSimpleResolver(keys), ForceFail(), app.NewJWTSecurity())
	return func(nextHandler goa.Handler) goa.Handler {
		return func(ctx context.Context, rw http.ResponseWriter, req *http.Request) error {
			err := jwtMiddleware(nextHandler)(ctx, rw, req)
			if err != nil {
				// err が起きた場合 nextHandlerは呼ばれないが、今回は処理を継続する
				return nextHandler(ctx, rw, req)
			}
			return nil
		}
	}, nil

}
```
``jwtMiddleware``の部分が元々のmiddlewareの部分ですが
``error``の場合は``nextHandler``の呼び出しを中止するので``error``の場合でも
強制的に``nextHandler``を呼んで処理を継続させる処理に変更しました。

使う側でのmiddleware呼び出しも忘れずに行います。

```
jwtMiddleware, _ := NewJWTMiddleware()
optionalJwtMiddleware, _ := NewOptionalJWTMiddleware() // <-- 追加
app.UseJWTMiddleware(service, jwtMiddleware)
app.UseOptionalJWTMiddleware(service, optionalJwtMiddleware) // <-- 追加
```

Action内の実装は以下のとおりです

```
// Optional runs the optional action.
func (c *JWTController) Optional(ctx *app.OptionalJWTContext) error {
	fmt.Println("in Optional")
	token := jwt.ContextJWT(ctx)
	if token != nil {
		claims := token.Claims.(jwtgo.MapClaims)
		fmt.Println(claims)
	} else {
		fmt.Println("token is nothing.")
	}

	res := &app.Success{OK: true}
	return ctx.OK(res)
}

```

``jwt.ContextJWT`` 関数にてcontextからtokenの取得ができます。