2020/07/04 - GitHub OAuth 구현하기 (1)
2020/07/07 - GitHub OAuth 구현하기 (2) 에서 이어집니다.
GitHub OAuth 구현을 실제 소스코드를 통해서 설명하려고 합니다. ( 사용한 언어는 Golang 이지만, 다른 언어를 사용하더라도 비슷한 방식으로 구현할 수 있습니다. )
먼저 init() 함수에서 OAuth관련 설정을 해 줍니다. 여기서 만든 설정은 전체 앱에서 사용합니다.
var OAuthConf *oauth2.Config
func init() {
OAuthConf = &oauth2.Config{
ClientID: "{{ oauth app client id }}",
ClientSecret: "{{ oauth app client secret }}",
RedirectURL: "{{ call back url }}",
Scopes: []string{"user"}, //app 권한
Endpoint: github.Endpoint,
}
}
"{{ }}" 표시된 부분은 실제 값을 넣어줘야 합니다. Scope는 OAuth 앱이 가지게 되는 권한을 명시해 주는 부분입니다. 여기서는 사용자 정보를 가져다 화면에 보여줄 것이기 때문에 사용자 정보를 얻어올 "user"만 적어줬습니다.
그리고 http 라우팅을 처리할 핸들러를 main()에 선언했습니다.
func main() {
gob.Register(User{})
http.HandleFunc("/", MainHandler)
http.HandleFunc("/login", LoginHandler)
http.HandleFunc("/auth/callback", CallbackHandler)
log.Fatal(http.ListenAndServe(":8000", nil))
}
mainHandler에서는 쿠키에 저장된 사용자 정보를 읽어 화면에 뿌려주거나, 없는 경우, 로그인 하도록 요청을 보냅니다.
func MainHandler(w http.ResponseWriter, r *http.Request) {
session, _ := store.Get(r, "session")
userInfo := session.Values["user"]
if userInfo == nil {
http.Redirect(w, r, "/login", http.StatusFound)
} else {
RenderHtmlTemplate(w, "main.html", userInfo)
}
}
로그인 요청을 받은 LoginHandler에서는 state 코드를 생성하고, GitHub에 OAuth앱 인증을 위해 필요한 정보( client_id, redirect_uri, scope, state )를 담아 "Sign in with GitHub" 링크를 생성합니다.
func LoginHandler(w http.ResponseWriter, r *http.Request) {
session, _ := store.Get(r, "session")
session.Options = &sessions.Options{
Path: "/auth",
MaxAge: 300,
}
state := uuid.New().String()
session.Values["state"] = state
session.Save(r, w)
RenderHtmlTemplate(w, "login.html", OAuthConf.AuthCodeURL(state))
}
여기서 state는 GitHub에서는 Callback에서 받은 대로 돌려주게 되며, Callback 요청에 대한 검증을 위해 사용합니다. 여기서는 300초 즉, 5분안에 Callback을 받지 못하거나, state가 일치하지 않는 경우, 에러로 처리합니다. 그리고 임의의 코드를 생성하기 위해 uuid를 생성하였으며, 임의의 문자열이면 되기 때문에 Random String을 생성하여 전송해되 됩니다.
CallbackHandler에서는 state를 확인하고, OAuth token을 얻은 다음 GitHub API를 이용해서 사용자 정보( Login, Email, AvatarURL )를 세선에 담고 루트 URL로 요청을 보냅니다.
func CallbackHandler(w http.ResponseWriter, r *http.Request) {
session, _ := store.Get(r, "session")
state := session.Values["state"]
delete(session.Values, "state")
session.Save(r, w)
if state != r.FormValue("state") {
http.Error(w, "Invalid session state", http.StatusUnauthorized)
return
}
token, err := OAuthConf.Exchange(context.Background(), r.FormValue("code"))
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
client := OAuthConf.Client(context.Background(), token)
ghClient := GH.NewClient(client)
user, _, err := ghClient.Users.Get(context.Background(), "")
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
authUser := User{
Name: user.GetLogin(),
Email: user.GetEmail(),
Avatar: user.GetAvatarURL(),
}
session.Options = &sessions.Options{
Path: "/",
MaxAge: 86400,
}
session.Values["user"] = authUser
session.Save(r, w)
http.Redirect(w, r, "/", http.StatusFound)
}
여기 까지가 GitHub OAuth 앱의 구현의 끝입니다.
지금 만든 앱으로 누군가 인증하면 GitHub의 OAuth앱 등록화면에서 (누가 사용하고 있는지는 알 수 없지만)사용자 수가 표시됩니다.
전체 소스코드는 GitHub ( https://github.com/ikaruce/go-github-oauth ) 에 공개되어 있고 https://github-oauth-sample.herokuapp.com/ 에서 동작하는 샘플을 사용해보실 수 있습니다. 다음 번에는 GitHub OAuth 앱을 배포하는 이야기를 마지막으로 GitHub OAuth 구현하기 시리즈는 마무리 하겠습니다.