golang oauth2.0 サーバー側の処理

##
この記事は実装の一部となります

## 図：
![image.png](https://www.infoscoop.org/blogjp/wp-content/uploads/2014/05/openidconnect00.png)
## pkg: 
```golang
import (
    "github.com/openshift/osin"
)
```
### 構造体：

```golang
// App ...
type App struct {
	OAuthServer  *osin.Server 
	DBClient     db.DB 
}
```

### DB周り (ID/Password認証情報など、実装が必要)
```golang
// DB ...
type DB interface {
	Close()
    CheckIDPassword(ID,Password) error
	CheckIDByFefresToken(refreshToken string) error
    //...
}

// ...
// 各メソッドの実装
```

### Storage周り (accesstoken/refreshtokenの情報など、実装が必要)
![image.png](https://qiita-image-store.s3.amazonaws.com/0/190518/1141ca0a-ad4a-9654-8dee-a36ad0f44100.png)
https://github.com/openshift/osin#storage-backends

```osin/storage.go
// Storage interface
type Storage interface {
	// Clone the storage if needed. For example, using mgo, you can clone the session with session.Clone
	// to avoid concurrent access problems.
	// This is to avoid cloning the connection at each method access.
	// Can return itself if not a problem.
	Clone() Storage

	// Close the resources the Storage potentially holds (using Clone for example)
	Close()

	// GetClient loads the client by id (client_id)
	GetClient(id string) (Client, error)

	// SaveAuthorize saves authorize data.
	SaveAuthorize(*AuthorizeData) error

	// LoadAuthorize looks up AuthorizeData by a code.
	// Client information MUST be loaded together.
	// Optionally can return error if expired.
	LoadAuthorize(code string) (*AuthorizeData, error)

	// RemoveAuthorize revokes or deletes the authorization code.
	RemoveAuthorize(code string) error

	// SaveAccess writes AccessData.
	// If RefreshToken is not blank, it must save in a way that can be loaded using LoadRefresh.
	SaveAccess(*AccessData) error

	// LoadAccess retrieves access data by token. Client information MUST be loaded together.
	// AuthorizeData and AccessData DON'T NEED to be loaded if not easily available.
	// Optionally can return error if expired.
	LoadAccess(token string) (*AccessData, error)

	// RemoveAccess revokes or deletes an AccessData.
	RemoveAccess(token string) error

	// LoadRefresh retrieves refresh AccessData. Client information MUST be loaded together.
	// AuthorizeData and AccessData DON'T NEED to be loaded if not easily available.
	// Optionally can return error if expired.
	LoadRefresh(token string) (*AccessData, error)

	// RemoveRefresh revokes or deletes refresh AccessData.
	RemoveRefresh(token string) error
}

```


## authorize_code 認証エンドポイント：

```golang
// Authorize ...
func (a *App) Authorize(w http.ResponseWriter, r *http.Request) {
	resp := a.OAuthServer.NewResponse()
	defer resp.Close()
	if ar := a.OAuthServer.HandleAuthorizeRequest(resp, r); ar != nil {
		id := r.FormValue("id")
		password := r.FormValue("password")

		//csrftoken checkなど
		//...

		//DBにUserとPassword確認
		err := a.DBClient.CheckIDPassword(id, password)
		if err == sql.ErrNoRows {

			// NoRows場合の処理

		} else if err != nil {

			// error処理

		}
		ar.Authorized = true
		a.OAuthServer.FinishAuthorizeRequest(resp, r, ar)
		osin.OutputJSON(resp, w, r)
	}
}

```

実際使ってるメソッドはライブラリーのHandleAuthorizeRequestメソッドなので、中身：
### HandleAuthorizeRequestの中身（一部分省略）

```osin/authorize.go
// authorization requests
func (s *Server) HandleAuthorizeRequest(w *Response, r *http.Request) *AuthorizeRequest {
	r.ParseForm()

	// create the authorization request 
	unescapedUri, err := url.QueryUnescape(r.FormValue("redirect_uri"))
	if err != nil {
		w.SetErrorState(E_INVALID_REQUEST, "", "")
		w.InternalError = err
		return nil
	}

	// ...

	ret := &AuthorizeRequest{
		State:       r.FormValue("state"),
		Scope:       r.FormValue("scope"),
		//...
	}

	// must have a valid client
	ret.Client, err = w.Storage.GetClient(r.FormValue("client_id"))
	if err == ErrNotFound {
		w.SetErrorState(E_UNAUTHORIZED_CLIENT, "", ret.State)
		return nil
	}

	// ...

	requestType := AuthorizeRequestType(r.FormValue("response_type"))
	if s.Config.AllowedAuthorizeTypes.Exists(requestType) {
		switch requestType {
		case CODE:
			// ...
		case TOKEN:
			// ...
		}
		return ret
	}
	//...
}

```

```golang
パラメターについて：

//response_type 必須 
アクセストークンを要求する場合は "token" を設定します。認可コードの場合は "code" を設定します。 

//client_id  必須 
クライアントを設定します。 

//redirect_uri 条件付き必須  
クライアントと認可サーバ間で事前にリダイレクト先の URI が決められていない場合は必須になります。 
エンドユーザによる認可が完了した際に、認可サーバがユーザーエージェントをリダイレクトする先の絶対 URI を設定します。

//scope 任意 
リクエストで要求するアクセス権のスコープを、空白区切りの文字列で指定します。 scopeの値に何を指定するかは認可サーバによって定義
されます。 空白区切りで複数の値を含む場合は、その順序に意味はありません。それぞれの値を合わせた範囲が要求するアクセス権になります。

//state 任意
リクエストから完了時のコールバックまでの間、クライアントが状態管理できる値を指定します。 
認可サーバは指定された値をそのままリダイレクトURIに、ユーザーエージェントをリダイレクトします。 
```

### Accesstoken、refreshTokenの発行、保存など
```golang
// Token ...
func (a *App) Token(w http.ResponseWriter, r *http.Request) {
	resp := a.OAuthServer.NewResponse()
	defer resp.Close()
	if ar := a.OAuthServer.HandleAccessRequest(resp, r); ar != nil {
		switch ar.Type {
		case osin.AUTHORIZATION_CODE:
			ar.Authorized = true
		case osin.REFRESH_TOKEN:

　　　　　　　// DBにtokenと紐ついてるuserの存在（退会の場合あり）を確認
			err := a.DBClient.CheckIDByFefresToken(ar.UserData.(string))
			
            //  NoRows and error処理 ...

			ar.Authorized = true
		case osin.PASSWORD:

            // DBにUserとPassword確認
			err := a.DBClient.CheckIDPassword(ar.Username, ar.Password)
			
            //  NoRows and error処理 ...

			ar.Authorized = true
		}

        // FinishAccessRequestが全部やってくれた
		a.OAuthServer.FinishAccessRequest(resp, r, ar)
	}

	if resp.InternalError != nil {

		// error処理 ...

	}
	osin.OutputJSON(resp, w, r)
}
```

### Accesstoken、refreshTokenの発行など　FinishAccessRequestがやってくれた
```osin/access.go

func (s *Server) FinishAccessRequest(w *Response, r *http.Request, ar *AccessRequest) {
	// don't process if is already an error
	if w.IsError {
		return
	}
	redirectUri := r.FormValue("redirect_uri")
	// Get redirect uri from AccessRequest if it's there (e.g., refresh token request)
	if ar.RedirectUri != "" {
		redirectUri = ar.RedirectUri
	}
	if ar.Authorized {
		var ret *AccessData
		var err error

		if ar.ForceAccessData == nil {
			// generate access token
			ret = &AccessData{
				Client:        ar.Client,
				AuthorizeData: ar.AuthorizeData,
				AccessData:    ar.AccessData,
				RedirectUri:   redirectUri,
				CreatedAt:     s.Now(),
				ExpiresIn:     ar.Expiration,
				UserData:      ar.UserData,
				Scope:         ar.Scope,
			}

			// accesstoken and refreshToken ここで発行する
			ret.AccessToken, ret.RefreshToken, err = s.AccessTokenGen.GenerateAccessToken(ret, ar.GenerateRefresh)
			if err != nil {
				s.setErrorAndLog(w, E_SERVER_ERROR, err, "finish_access_request=%s", "error generating token")
				return
			}
		} else {
			ret = ar.ForceAccessData
		}

		// DBに保存までやってくれる、インタフェース：Storegeの実装が必要
		if err = w.Storage.SaveAccess(ret); err != nil {
			s.setErrorAndLog(w, E_SERVER_ERROR, err, "finish_access_request=%s", "error saving access token")
			return
		}

		// ...

		// output data
		w.Output["access_token"] = ret.AccessToken
		w.Output["token_type"] = s.Config.TokenType
		w.Output["expires_in"] = ret.ExpiresIn
		if ret.RefreshToken != "" {
			w.Output["refresh_token"] = ret.RefreshToken
		}
		if ret.Scope != "" {
			w.Output["scope"] = ret.Scope
		}
	} else {
		s.setErrorAndLog(w, E_ACCESS_DENIED, nil, "finish_access_request=%s", "authorization failed")
	}
}

```

### 参考：
https://murashun.jp/blog/20150920-01.html
https://www.infoscoop.org/blogjp/2014/05/12/1858/